Merge branch 'devel/master' into tizen
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / image-operations.cpp
1 /*
2  * Copyright (c) 2018 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 <dali/internal/imaging/common/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 <third-party/resampler/resampler.h>
29 #include <dali/devel-api/adaptor-framework/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 const float RAD_135 = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians;
55 const float RAD_225 = RAD_135    + Math::PI_2; ///< 225 degrees in radians;
56 const float RAD_270 = 3.f * Math::PI_2;        ///< 270 degrees in radians;
57 const float RAD_315 = RAD_225    + Math::PI_2; ///< 315 degrees in radians;
58
59 using Integration::Bitmap;
60 using Integration::BitmapPtr;
61 typedef unsigned char PixelBuffer;
62
63 /**
64  * @brief 4 byte pixel structure.
65  */
66 struct Pixel4Bytes
67 {
68   uint8_t r;
69   uint8_t g;
70   uint8_t b;
71   uint8_t a;
72 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
73
74 /**
75  * @brief RGB888 pixel structure.
76  */
77 struct Pixel3Bytes
78 {
79   uint8_t r;
80   uint8_t g;
81   uint8_t b;
82 } __attribute__((packed, aligned(1)));
83
84 /**
85  * @brief RGB565 pixel typedefed from a short.
86  *
87  * Access fields by manual shifting and masking.
88  */
89 typedef uint16_t PixelRGB565;
90
91 /**
92  * @brief a Pixel composed of two independent byte components.
93  */
94 struct Pixel2Bytes
95 {
96   uint8_t l;
97   uint8_t a;
98 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
99
100
101 #if defined(DEBUG_ENABLED)
102 /**
103  * Disable logging of image operations or make it verbose from the commandline
104  * as follows (e.g., for dali demo app):
105  * <code>
106  * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
107  * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
108  * </code>
109  */
110 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
111 #endif
112
113 /** @return The greatest even number less than or equal to the argument. */
114 inline unsigned int EvenDown( const unsigned int a )
115 {
116   const unsigned int evened = a & ~1u;
117   return evened;
118 }
119
120 /**
121  * @brief Log bad parameters.
122  */
123 void ValidateScalingParameters( const unsigned int inputWidth,
124                                 const unsigned int inputHeight,
125                                 const unsigned int desiredWidth,
126                                 const unsigned int desiredHeight )
127 {
128   if( desiredWidth > inputWidth || desiredHeight > inputHeight )
129   {
130     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
131   }
132
133   if( desiredWidth == 0u || desiredHeight == 0u )
134   {
135     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n" );
136   }
137
138   if( inputWidth == 0u || inputHeight == 0u )
139   {
140     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n" );
141   }
142 }
143
144 /**
145  * @brief Do debug assertions common to all scanline halving functions.
146  * @note Inline and in anon namespace so should boil away in release builds.
147  */
148 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
149 {
150   DALI_ASSERT_DEBUG( pixels && "Null pointer." );
151   DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
152   DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
153 }
154
155 /**
156  * @brief Assertions on params to functions averaging pairs of scanlines.
157  * @note Inline as intended to boil away in release.
158  */
159 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
160                                                const uint8_t * const scanline2,
161                                                uint8_t* const outputScanline,
162                                                const size_t widthInComponents )
163 {
164   DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
165   DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
166   DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
167   DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
168   DALI_ASSERT_DEBUG( ((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
169 }
170
171 /**
172  * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
173  */
174 BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
175 {
176   BoxDimensionTest dimensionTest;
177   dimensionTest = BoxDimensionTestEither;
178
179   switch( fittingMode )
180   {
181     // Shrink to fit attempts to make one or zero dimensions smaller than the
182     // desired dimensions and one or two dimensions exactly the same as the desired
183     // ones, so as long as one dimension is larger than the desired size, box
184     // filtering can continue even if the second dimension is smaller than the
185     // desired dimensions:
186     case FittingMode::SHRINK_TO_FIT:
187     {
188       dimensionTest = BoxDimensionTestEither;
189       break;
190     }
191     // Scale to fill mode keeps both dimensions at least as large as desired:
192     case FittingMode::SCALE_TO_FILL:
193     {
194       dimensionTest = BoxDimensionTestBoth;
195       break;
196     }
197     // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
198     case FittingMode::FIT_WIDTH:
199     {
200       dimensionTest = BoxDimensionTestX;
201       break;
202     }
203     // X Dimension is ignored by definition in FIT_HEIGHT mode:
204     case FittingMode::FIT_HEIGHT:
205     {
206       dimensionTest = BoxDimensionTestY;
207       break;
208     }
209   }
210
211   return dimensionTest;
212 }
213
214 /**
215  * @brief Work out the dimensions for a uniform scaling of the input to map it
216  * into the target while effecting ShinkToFit scaling mode.
217  */
218 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
219 {
220   // Scale the input by the least extreme of the two dimensions:
221   const float widthScale  = target.GetX() / float(source.GetX());
222   const float heightScale = target.GetY() / float(source.GetY());
223   const float scale = widthScale < heightScale ? widthScale : heightScale;
224
225   // Do no scaling at all if the result would increase area:
226   if( scale >= 1.0f )
227   {
228     return source;
229   }
230
231   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
232 }
233
234 /**
235  * @brief Work out the dimensions for a uniform scaling of the input to map it
236  * into the target while effecting SCALE_TO_FILL scaling mode.
237  * @note An image scaled into the output dimensions will need either top and
238  * bottom or left and right to be cropped away unless the source was pre-cropped
239  * to match the destination aspect ratio.
240  */
241 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
242 {
243   DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0  && "Zero-area rectangles should not be passed-in" );
244   // Scale the input by the least extreme of the two dimensions:
245   const float widthScale  = target.GetX() / float(source.GetX());
246   const float heightScale = target.GetY() / float(source.GetY());
247   const float scale = widthScale > heightScale ? widthScale : heightScale;
248
249   // Do no scaling at all if the result would increase area:
250   if( scale >= 1.0f )
251   {
252     return source;
253   }
254
255   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
256 }
257
258 /**
259  * @brief Work out the dimensions for a uniform scaling of the input to map it
260  * into the target while effecting FIT_WIDTH scaling mode.
261  */
262 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
263 {
264   DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
265   const float scale  = target.GetX() / float(source.GetX());
266
267   // Do no scaling at all if the result would increase area:
268   if( scale >= 1.0f )
269   {
270    return source;
271   }
272   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
273 }
274
275 /**
276  * @brief Work out the dimensions for a uniform scaling of the input to map it
277  * into the target while effecting FIT_HEIGHT scaling mode.
278  */
279 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
280 {
281   DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
282   const float scale = target.GetY() / float(source.GetY());
283
284   // Do no scaling at all if the result would increase area:
285   if( scale >= 1.0f )
286   {
287     return source;
288   }
289
290   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
291 }
292
293 /**
294  * @brief Generate the rectangle to use as the target of a pixel sampling pass
295  * (e.g., nearest or linear).
296  */
297 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode )
298 {
299   ImageDimensions fitDimensions;
300   switch( fittingMode )
301   {
302     case FittingMode::SHRINK_TO_FIT:
303     {
304       fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
305       break;
306     }
307     case FittingMode::SCALE_TO_FILL:
308     {
309       fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
310       break;
311     }
312     case FittingMode::FIT_WIDTH:
313     {
314       fitDimensions = FitForFitWidth( requestedSize, sourceSize );
315       break;
316     }
317     case FittingMode::FIT_HEIGHT:
318     {
319       fitDimensions = FitForFitHeight( requestedSize, sourceSize );
320       break;
321     }
322   }
323
324   return fitDimensions;
325 }
326
327 /**
328  * @brief Calculate the number of lines on the X and Y axis that need to be
329  * either added or removed with repect to the specified fitting mode.
330  * (e.g., nearest or linear).
331  * @param[in]     sourceSize      The size of the source image
332  * @param[in]     fittingMode     The fitting mode to use
333  * @param[in/out] requestedSize   The target size that the image will be fitted to.
334  *                                If the source image is smaller than the requested size, the source is not scaled up.
335  *                                So we reduce the target size while keeping aspect by lowering resolution.
336  * @param[out]    scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
337  * @param[out]    columnsToCrop   The number of columns to remove from the image (can be negative to represent X borders required)
338  */
339 void CalculateBordersFromFittingMode(  ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
340 {
341   const unsigned int sourceWidth( sourceSize.GetWidth() );
342   const unsigned int sourceHeight( sourceSize.GetHeight() );
343   const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) );
344   int finalWidth = 0;
345   int finalHeight = 0;
346
347   switch( fittingMode )
348   {
349     case FittingMode::FIT_WIDTH:
350     {
351       finalWidth = sourceWidth;
352       finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
353
354       columnsToCrop = 0;
355       scanlinesToCrop = -( finalHeight - sourceHeight );
356       break;
357     }
358
359     case FittingMode::FIT_HEIGHT:
360     {
361       finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
362       finalHeight = sourceHeight;
363
364       columnsToCrop = -( finalWidth - sourceWidth );
365       scanlinesToCrop = 0;
366       break;
367     }
368
369     case FittingMode::SHRINK_TO_FIT:
370     {
371       const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
372       if( sourceAspect > targetAspect )
373       {
374         finalWidth = sourceWidth;
375         finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
376
377         columnsToCrop = 0;
378         scanlinesToCrop = -( finalHeight - sourceHeight );
379       }
380       else
381       {
382         finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
383         finalHeight = sourceHeight;
384
385         columnsToCrop = -( finalWidth - sourceWidth );
386         scanlinesToCrop = 0;
387       }
388       break;
389     }
390
391     case FittingMode::SCALE_TO_FILL:
392     {
393       const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
394       if( sourceAspect > targetAspect )
395       {
396         finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
397         finalHeight = sourceHeight;
398
399         columnsToCrop = -( finalWidth - sourceWidth );
400         scanlinesToCrop = 0;
401       }
402       else
403       {
404         finalWidth = sourceWidth;
405         finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
406
407         columnsToCrop = 0;
408         scanlinesToCrop = -( finalHeight - sourceHeight );
409       }
410       break;
411     }
412   }
413
414   requestedSize.SetWidth( finalWidth );
415   requestedSize.SetHeight( finalHeight );
416 }
417
418 /**
419  * @brief Construct a pixel buffer object from a copy of the pixel array passed in.
420  */
421 Dali::Devel::PixelBuffer MakePixelBuffer( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
422 {
423   DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
424
425   // Allocate a pixel buffer to hold the image passed in:
426   auto newBitmap = Dali::Devel::PixelBuffer::New( width, height, pixelFormat );
427
428   // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
429   memcpy( newBitmap.GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
430   return newBitmap;
431 }
432
433 /**
434  * @brief Work out the desired width and height, accounting for zeros.
435  *
436  * @param[in] bitmapWidth Width of image before processing.
437  * @param[in] bitmapHeight Height of image before processing.
438  * @param[in] requestedWidth Width of area to scale image into. Can be zero.
439  * @param[in] requestedHeight Height of area to scale image into. Can be zero.
440  * @return Dimensions of area to scale image into after special rules are applied.
441  */
442 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
443 {
444   unsigned int maxSize = Dali::GetMaxTextureSize();
445
446   // If no dimensions have been requested, default to the source ones:
447   if( requestedWidth == 0 && requestedHeight == 0 )
448   {
449     if( bitmapWidth <= maxSize && bitmapHeight <= maxSize )
450     {
451       return ImageDimensions( bitmapWidth, bitmapHeight );
452     }
453     else
454     {
455       // Calculate the size from the max texture size and the source image aspect ratio
456       if( bitmapWidth > bitmapHeight )
457       {
458         return ImageDimensions( maxSize, bitmapHeight * maxSize / static_cast< float >( bitmapWidth ) + 0.5f );
459       }
460       else
461       {
462         return ImageDimensions( bitmapWidth * maxSize / static_cast< float >( bitmapHeight ) + 0.5f, maxSize );
463       }
464     }
465   }
466
467   // If both dimensions have values requested, use them both:
468   if( requestedWidth != 0 && requestedHeight != 0 )
469   {
470     if( requestedWidth <= maxSize && requestedHeight <= maxSize )
471     {
472       return ImageDimensions( requestedWidth, requestedHeight );
473     }
474     else
475     {
476       // Calculate the size from the max texture size and the source image aspect ratio
477       if( requestedWidth > requestedHeight )
478       {
479         return ImageDimensions( maxSize, requestedHeight * maxSize / static_cast< float >( requestedWidth ) + 0.5f );
480       }
481       else
482       {
483         return ImageDimensions( requestedWidth * maxSize / static_cast< float >( requestedHeight ) + 0.5f, maxSize );
484       }
485     }
486   }
487
488   // Only one of the dimensions has been requested. Calculate the other from
489   // the requested one and the source image aspect ratio:
490   if( requestedWidth != 0 )
491   {
492     requestedWidth = std::min( requestedWidth, maxSize );
493     return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
494   }
495
496   requestedHeight = std::min( requestedHeight, maxSize );
497   return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
498 }
499
500 /**
501  * @brief Rotates the given buffer @p pixelsIn 90 degrees counter clockwise.
502  *
503  * @note It allocates memory for the returned @p pixelsOut buffer.
504  * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
505  *
506  * @param[in] pixelsIn The input buffer.
507  * @param[in] widthIn The width of the input buffer.
508  * @param[in] heightIn The height of the input buffer.
509  * @param[in] pixelSize The size of the pixel.
510  * @param[out] pixelsOut The rotated output buffer.
511  * @param[out] widthOut The width of the output buffer.
512  * @param[out] heightOut The height of the output buffer.
513  */
514 void Rotate90( const uint8_t* const pixelsIn,
515                unsigned int widthIn,
516                unsigned int heightIn,
517                unsigned int pixelSize,
518                uint8_t*& pixelsOut,
519                unsigned int& widthOut,
520                unsigned int& heightOut )
521 {
522   // The new size of the image.
523   widthOut = heightIn;
524   heightOut = widthIn;
525
526   // Allocate memory for the rotated buffer.
527   pixelsOut = static_cast<uint8_t*>( malloc ( widthOut * heightOut * pixelSize ) );
528
529   // Rotate the buffer.
530   for( unsigned int y = 0u; y < heightIn; ++y )
531   {
532     const unsigned int srcLineIndex = y * widthIn;
533     const unsigned int dstX = y;
534     for( unsigned int x = 0u; x < widthIn; ++x )
535     {
536       const unsigned int dstY = heightOut - x - 1u;
537       const unsigned int dstIndex = pixelSize * ( dstY * widthOut + dstX );
538       const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
539
540       for( unsigned int channel = 0u; channel < pixelSize; ++channel )
541       {
542         *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
543       }
544     }
545   }
546 }
547
548 /**
549  * @brief Rotates the given buffer @p pixelsIn 180 degrees counter clockwise.
550  *
551  * @note It allocates memory for the returned @p pixelsOut buffer.
552  * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
553  *
554  * @param[in] pixelsIn The input buffer.
555  * @param[in] widthIn The width of the input buffer.
556  * @param[in] heightIn The height of the input buffer.
557  * @param[in] pixelSize The size of the pixel.
558  * @param[out] pixelsOut The rotated output buffer.
559  */
560 void Rotate180( const uint8_t* const pixelsIn,
561                 unsigned int widthIn,
562                 unsigned int heightIn,
563                 unsigned int pixelSize,
564                 uint8_t*& pixelsOut )
565 {
566   // Allocate memory for the rotated buffer.
567   pixelsOut = static_cast<uint8_t*>( malloc ( widthIn * heightIn * pixelSize ) );
568
569   // Rotate the buffer.
570   for( unsigned int y = 0u; y < heightIn; ++y )
571   {
572     const unsigned int srcLineIndex = y * widthIn;
573     const unsigned int dstY = heightIn - y - 1u;
574     for( unsigned int x = 0u; x < widthIn; ++x )
575     {
576       const unsigned int dstX = widthIn - x - 1u;
577       const unsigned int dstIndex = pixelSize * ( dstY * widthIn + dstX );
578       const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
579
580       for( unsigned int channel = 0u; channel < pixelSize; ++channel )
581       {
582         *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
583       }
584     }
585   }
586 }
587
588 /**
589  * @brief Rotates the given buffer @p pixelsIn 270 degrees counter clockwise.
590  *
591  * @note It allocates memory for the returned @p pixelsOut buffer.
592  * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
593  *
594  * @param[in] pixelsIn The input buffer.
595  * @param[in] widthIn The width of the input buffer.
596  * @param[in] heightIn The height of the input buffer.
597  * @param[in] pixelSize The size of the pixel.
598  * @param[out] pixelsOut The rotated output buffer.
599  * @param[out] widthOut The width of the output buffer.
600  * @param[out] heightOut The height of the output buffer.
601  */
602 void Rotate270( const uint8_t* const pixelsIn,
603                 unsigned int widthIn,
604                 unsigned int heightIn,
605                 unsigned int pixelSize,
606                 uint8_t*& pixelsOut,
607                 unsigned int& widthOut,
608                 unsigned int& heightOut )
609 {
610   // The new size of the image.
611   widthOut = heightIn;
612   heightOut = widthIn;
613
614   // Allocate memory for the rotated buffer.
615   pixelsOut = static_cast<uint8_t*>( malloc ( widthOut * heightOut * pixelSize ) );
616
617   // Rotate the buffer.
618   for( unsigned int y = 0u; y < heightIn; ++y )
619   {
620     const unsigned int srcLineIndex = y * widthIn;
621     const unsigned int dstX = widthOut - y - 1u;
622     for( unsigned int x = 0u; x < widthIn; ++x )
623     {
624       const unsigned int dstY = x;
625       const unsigned int dstIndex = pixelSize * ( dstY * widthOut + dstX );
626       const unsigned int srcIndex = pixelSize * ( srcLineIndex + x );
627
628       for( unsigned int channel = 0u; channel < pixelSize; ++channel )
629       {
630         *( pixelsOut + dstIndex + channel ) = *( pixelsIn + srcIndex + channel );
631       }
632     }
633   }
634 }
635
636 /**
637  * @brief Skews a row horizontally (with filtered weights)
638  *
639  * @note Limited to 45 degree skewing only.
640  * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
641  *
642  * @param[in] srcBufferPtr Pointer to the input pixel buffer.
643  * @param[in] srcWidth The width of the input pixel buffer.
644  * @param[in] pixelSize The size of the pixel.
645  * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
646  * @param[in] dstWidth The width of the output pixel buffer.
647  * @param[in] row The row index.
648  * @param[in] offset The skew offset.
649  * @param[in] weight The relative weight of right pixel.
650  */
651 void HorizontalSkew( const uint8_t* const srcBufferPtr,
652                      int srcWidth,
653                      unsigned int pixelSize,
654                      uint8_t*& dstBufferPtr,
655                      int dstWidth,
656                      unsigned int row,
657                      int offset,
658                      float weight )
659 {
660   if( offset > 0 )
661   {
662     // Fill gap left of skew with background.
663     memset( dstBufferPtr + row * pixelSize * dstWidth, 0u, pixelSize * offset );
664   }
665
666   unsigned char oldLeft[4u] = { 0u, 0u, 0u, 0u };
667
668   int i = 0;
669   for( i = 0u; i < srcWidth; ++i )
670   {
671     // Loop through row pixels
672     const unsigned int srcIndex = pixelSize * ( row * srcWidth + i );
673
674     unsigned char src[4u] = { 0u, 0u, 0u, 0u };
675     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
676     {
677       src[channel] = *( srcBufferPtr + srcIndex + channel );
678     }
679
680     // Calculate weights
681     unsigned char left[4u] = { 0u, 0u, 0u, 0u };
682     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
683     {
684       left[channel] = static_cast<unsigned char>( static_cast<float>( src[channel] ) * weight );
685
686       // Update left over on source
687       src[channel] -= ( left[channel] - oldLeft[channel] );
688     }
689
690     // Check boundaries
691     if( ( i + offset >= 0 ) && ( i + offset < dstWidth ) )
692     {
693       const unsigned int dstIndex = pixelSize * ( row * dstWidth + i + offset );
694
695       for( unsigned int channel = 0u; channel < pixelSize; ++channel )
696       {
697         *( dstBufferPtr + dstIndex + channel ) = src[channel];
698       }
699     }
700
701     // Save leftover for next pixel in scan
702     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
703     {
704       oldLeft[channel] = left[channel];
705     }
706   }
707
708   // Go to rightmost point of skew
709   i += offset;
710   if( i < dstWidth )
711   {
712     // If still in image bounds, put leftovers there
713     const unsigned int dstIndex = pixelSize * ( row * dstWidth + i );
714
715     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
716     {
717       *( dstBufferPtr + dstIndex + channel ) = oldLeft[channel];
718     }
719
720     // Clear to the right of the skewed line with background
721     ++i;
722     memset( dstBufferPtr + pixelSize * ( row * dstWidth + i ), 0u, pixelSize * ( dstWidth - i ) );
723   }
724 }
725
726 /**
727  * @brief Skews a column vertically (with filtered weights)
728  *
729  * @note Limited to 45 degree skewing only.
730  * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
731  *
732  * @param[in] srcBufferPtr Pointer to the input pixel buffer.
733  * @param[in] srcWidth The width of the input pixel buffer.
734  * @param[in] srcHeight The height of the input pixel buffer.
735  * @param[in] pixelSize The size of the pixel.
736  * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
737  * @param[in] dstWidth The width of the output pixel buffer.
738  * @param[in] dstHeight The height of the output pixel buffer.
739  * @param[in] column The column index.
740  * @param[in] offset The skew offset.
741  * @param[in] weight The relative weight of uppeer pixel.
742  */
743 void VerticalSkew( const uint8_t* const srcBufferPtr,
744                    int srcWidth,
745                    int srcHeight,
746                    unsigned int pixelSize,
747                    uint8_t*& dstBufferPtr,
748                    int dstWidth,
749                    int dstHeight,
750                    unsigned int column,
751                    int offset,
752                    float weight )
753 {
754   for( int i = 0; i < offset; ++i )
755   {
756     // Fill gap above skew with background
757     const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
758
759     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
760     {
761       *( dstBufferPtr + dstIndex + channel ) = 0u;
762     }
763   }
764
765   unsigned char oldLeft[4u] = { 0u, 0u, 0u, 0u };
766
767   int yPos = 0;
768   int i = 0;
769   for( i = 0; i < srcHeight; ++i )
770   {
771     // Loop through column pixels
772     const unsigned int srcIndex = pixelSize * ( i * srcWidth + column );
773
774     unsigned char src[4u] = { 0u, 0u, 0u, 0u };
775     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
776     {
777       src[channel] = *( srcBufferPtr + srcIndex + channel );
778     }
779
780     yPos = i + offset;
781
782     // Calculate weights
783     unsigned char left[4u] = { 0u, 0u, 0u, 0u };
784     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
785     {
786       left[channel] = static_cast<unsigned char>( static_cast<float>( src[channel] ) * weight );
787       // Update left over on source
788       src[channel] -= ( left[channel] - oldLeft[channel] );
789     }
790
791     // Check boundaries
792     if( ( yPos >= 0 ) && ( yPos < dstHeight ) )
793     {
794       const unsigned int dstIndex = pixelSize * ( yPos * dstWidth + column );
795
796       for( unsigned int channel = 0u; channel < pixelSize; ++channel )
797       {
798         *( dstBufferPtr + dstIndex + channel ) = src[channel];
799       }
800     }
801
802     // Save leftover for next pixel in scan
803     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
804     {
805       oldLeft[channel] = left[channel];
806     }
807   }
808
809   // Go to bottom point of skew
810   i = yPos;
811   if( i < dstHeight )
812   {
813     // If still in image bounds, put leftovers there
814     const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
815
816     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
817     {
818       *( dstBufferPtr + dstIndex + channel ) = oldLeft[channel];
819     }
820   }
821
822   while( ++i < dstHeight )
823   {
824     // Clear below skewed line with background
825     const unsigned int dstIndex = pixelSize * ( i * dstWidth + column );
826
827     for( unsigned int channel = 0u; channel < pixelSize; ++channel )
828     {
829       *( dstBufferPtr + dstIndex + channel ) = 0u;
830     }
831   }
832 }
833
834 } // namespace - unnamed
835
836 ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
837 {
838   return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
839 }
840
841 /**
842  * @brief Apply cropping and padding for specified fitting mode.
843  *
844  * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
845  * based on the fitting mode.
846  *
847  * This will add vertical or horizontal borders if necessary.
848  * Crop the source image data vertically or horizontally if necessary.
849  * The aspect of the source image is preserved.
850  * If the source image is smaller than the desired size, the algorithm will modify the the newly created
851  *   bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
852  *   GPU scaling to be performed at render time giving the same result with less texture traversal.
853  *
854  * @param[in] bitmap            The source pixel buffer to perform modifications on.
855  * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
856  * @param[in] fittingMode       The fitting mode to use.
857  *
858  * @return                      A new bitmap with the padding and cropping required for fitting mode applied.
859  *                              If no modification is needed or possible, the passed in bitmap is returned.
860  */
861 Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode );
862
863 /**
864  * @brief Adds horizontal or vertical borders to the source image.
865  *
866  * @param[in] targetPixels     The destination image pointer to draw the borders on.
867  * @param[in] bytesPerPixel    The number of bytes per pixel of the target pixel buffer.
868  * @param[in] targetDimensions The dimensions of the destination image.
869  * @param[in] padDimensions    The columns and scanlines to pad with borders.
870  */
871 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
872
873 Dali::Devel::PixelBuffer ApplyAttributesToBitmap( Dali::Devel::PixelBuffer bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
874 {
875   if( bitmap )
876   {
877     // Calculate the desired box, accounting for a possible zero component:
878     const ImageDimensions desiredDimensions  = CalculateDesiredDimensions( bitmap.GetWidth(), bitmap.GetHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
879
880     // If a different size than the raw one has been requested, resize the image
881     // maximally using a repeated box filter without making it smaller than the
882     // requested size in either dimension:
883     bitmap = DownscaleBitmap( bitmap, desiredDimensions, fittingMode, samplingMode );
884
885     // Cut the bitmap according to the desired width and height so that the
886     // resulting bitmap has the same aspect ratio as the desired dimensions.
887     // Add crop and add borders if necessary depending on fitting mode.
888     if( bitmap )
889     {
890       bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
891     }
892   }
893
894   return bitmap;
895 }
896
897 Dali::Devel::PixelBuffer CropAndPadForFittingMode( Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
898 {
899   const unsigned int inputWidth = bitmap.GetWidth();
900   const unsigned int inputHeight = bitmap.GetHeight();
901
902   if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
903   {
904     DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
905   }
906   else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
907   {
908     // Calculate any padding or cropping that needs to be done based on the fitting mode.
909     // Note: If the desired size is larger than the original image, the desired size will be
910     // reduced while maintaining the aspect, in order to save unnecessary memory usage.
911     int scanlinesToCrop = 0;
912     int columnsToCrop = 0;
913
914     CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
915
916     unsigned int desiredWidth( desiredDimensions.GetWidth() );
917     unsigned int desiredHeight( desiredDimensions.GetHeight() );
918
919     // Action the changes by making a new bitmap with the central part of the loaded one if required.
920     if( scanlinesToCrop != 0 || columnsToCrop != 0 )
921     {
922       // Split the adding and removing of scanlines and columns into separate variables,
923       // so we can use one piece of generic code to action the changes.
924       unsigned int scanlinesToPad = 0;
925       unsigned int columnsToPad = 0;
926       if( scanlinesToCrop < 0 )
927       {
928         scanlinesToPad = -scanlinesToCrop;
929         scanlinesToCrop = 0;
930       }
931       if( columnsToCrop < 0 )
932       {
933         columnsToPad = -columnsToCrop;
934         columnsToCrop = 0;
935       }
936
937       // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
938       if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
939           ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
940       {
941         DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
942         return bitmap;
943       }
944
945       // Create new PixelBuffer with the desired size.
946       const auto pixelFormat = bitmap.GetPixelFormat();
947
948       auto croppedBitmap = Devel::PixelBuffer::New( desiredWidth, desiredHeight, pixelFormat );
949
950       // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
951       // The cropping is added to the source pointer, and the padding is added to the destination.
952       const auto bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
953       const PixelBuffer * const sourcePixels = bitmap.GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
954       PixelBuffer * const targetPixels = croppedBitmap.GetBuffer();
955       PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
956       DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
957
958       // Copy the image data to the new bitmap.
959       // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
960       unsigned int outputSpan( desiredWidth * bytesPerPixel );
961       if( columnsToCrop == 0 && columnsToPad == 0 )
962       {
963         memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
964       }
965       else
966       {
967         // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
968         // Precalculate any constants to optimize the inner loop.
969         const unsigned int inputSpan( inputWidth * bytesPerPixel );
970         const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
971         const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
972
973         for( unsigned int y = 0; y < scanlinesToCopy; ++y )
974         {
975           memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
976         }
977       }
978
979       // Add vertical or horizontal borders to the final image (if required).
980       desiredDimensions.SetWidth( desiredWidth );
981       desiredDimensions.SetHeight( desiredHeight );
982       AddBorders( croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
983       // Overwrite the loaded bitmap with the cropped version
984       bitmap = croppedBitmap;
985     }
986   }
987
988   return bitmap;
989 }
990
991 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
992 {
993   // Assign ints for faster access.
994   unsigned int desiredWidth( targetDimensions.GetWidth() );
995   unsigned int desiredHeight( targetDimensions.GetHeight() );
996   unsigned int columnsToPad( padDimensions.GetWidth() );
997   unsigned int scanlinesToPad( padDimensions.GetHeight() );
998   unsigned int outputSpan( desiredWidth * bytesPerPixel );
999
1000   // Add letterboxing (symmetrical borders) if needed.
1001   if( scanlinesToPad > 0 )
1002   {
1003     // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
1004     memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
1005
1006     // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
1007     // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
1008     unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
1009
1010     // Bottom border.
1011     memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
1012   }
1013   else if( columnsToPad > 0 )
1014   {
1015     // Add a left and right border.
1016     // Left:
1017     // Pre-calculate span size outside of loop.
1018     unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
1019     for( unsigned int y = 0; y < desiredHeight; ++y )
1020     {
1021       memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
1022     }
1023
1024     // Right:
1025     // Pre-calculate the initial x offset as it is always the same for a small optimization.
1026     // We subtract columnsToPad/2 from columnsToPad so that we have the correct
1027     // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
1028     unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
1029     PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
1030     unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
1031
1032     for( unsigned int y = 0; y < desiredHeight; ++y )
1033     {
1034       memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
1035     }
1036   }
1037 }
1038
1039 Dali::Devel::PixelBuffer DownscaleBitmap( Dali::Devel::PixelBuffer bitmap,
1040                                         ImageDimensions desired,
1041                                         FittingMode::Type fittingMode,
1042                                         SamplingMode::Type samplingMode )
1043 {
1044   // Source dimensions as loaded from resources (e.g. filesystem):
1045   auto bitmapWidth  = bitmap.GetWidth();
1046   auto bitmapHeight = bitmap.GetHeight();
1047   // Desired dimensions (the rectangle to fit the source image to):
1048   auto desiredWidth = desired.GetWidth();
1049   auto desiredHeight = desired.GetHeight();
1050
1051   Dali::Devel::PixelBuffer outputBitmap { bitmap };
1052
1053   // If a different size than the raw one has been requested, resize the image:
1054   if(
1055       (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
1056       ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
1057   {
1058     auto pixelFormat = bitmap.GetPixelFormat();
1059
1060     // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
1061     unsigned int shrunkWidth = -1, shrunkHeight = -1;
1062     DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight );
1063
1064     // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
1065     const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), fittingMode );
1066     const unsigned int filteredWidth = filteredDimensions.GetWidth();
1067     const unsigned int filteredHeight = filteredDimensions.GetHeight();
1068
1069     // Run a filter to scale down the bitmap if it needs it:
1070     bool filtered = false;
1071     if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
1072     {
1073       if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
1074           samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
1075       {
1076         outputBitmap = Dali::Devel::PixelBuffer::New( filteredWidth, filteredHeight, pixelFormat );
1077
1078         if( outputBitmap )
1079         {
1080           if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1081           {
1082             LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap.GetBuffer(), filteredDimensions );
1083           }
1084           else
1085           {
1086             PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap.GetBuffer(), filteredWidth, filteredHeight );
1087           }
1088           filtered = true;
1089         }
1090       }
1091     }
1092     // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
1093     if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
1094     {
1095       outputBitmap = MakePixelBuffer( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
1096     }
1097   }
1098
1099   return outputBitmap;
1100 }
1101
1102 namespace
1103 {
1104 /**
1105  * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
1106  * @param test Which combination of the two dimensions matter for terminating the filtering.
1107  * @param scaledWidth The width of the current downscaled image.
1108  * @param scaledHeight The height of the current downscaled image.
1109  * @param desiredWidth The target width for the downscaling.
1110  * @param desiredHeight The target height for the downscaling.
1111  */
1112 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
1113 {
1114   bool keepScaling = false;
1115   const unsigned int nextWidth = scaledWidth >> 1u;
1116   const unsigned int nextHeight = scaledHeight >> 1u;
1117
1118   if( nextWidth >= 1u && nextHeight >= 1u )
1119   {
1120     switch( test )
1121     {
1122       case BoxDimensionTestEither:
1123       {
1124         keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
1125         break;
1126       }
1127       case BoxDimensionTestBoth:
1128       {
1129         keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
1130         break;
1131       }
1132       case BoxDimensionTestX:
1133       {
1134         keepScaling = nextWidth >= desiredWidth;
1135         break;
1136       }
1137       case BoxDimensionTestY:
1138       {
1139         keepScaling = nextHeight >= desiredHeight;
1140         break;
1141       }
1142     }
1143   }
1144
1145   return keepScaling;
1146 }
1147
1148 /**
1149  * @brief A shared implementation of the overall iterative box filter
1150  * downscaling algorithm.
1151  *
1152  * Specialise this for particular pixel formats by supplying the number of bytes
1153  * per pixel and two functions: one for averaging pairs of neighbouring pixels
1154  * on a single scanline, and a second for averaging pixels at corresponding
1155  * positions on different scanlines.
1156  **/
1157 template<
1158   int BYTES_PER_PIXEL,
1159   void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
1160   void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
1161 >
1162 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
1163                                   const unsigned int inputWidth,
1164                                   const unsigned int inputHeight,
1165                                   const unsigned int desiredWidth,
1166                                   const unsigned int desiredHeight,
1167                                   BoxDimensionTest dimensionTest,
1168                                   unsigned& outWidth,
1169                                   unsigned& outHeight )
1170 {
1171   if( pixels == 0 )
1172   {
1173     return;
1174   }
1175   ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
1176
1177   // Scale the image until it would be smaller than desired, stopping if the
1178   // resulting height or width would be less than 1:
1179   unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
1180   while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
1181   {
1182     const unsigned int lastWidth = scaledWidth;
1183     scaledWidth  >>= 1u;
1184     scaledHeight >>= 1u;
1185
1186     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
1187
1188     const unsigned int lastScanlinePair = scaledHeight - 1;
1189
1190     // Scale pairs of scanlines until any spare one at the end is dropped:
1191     for( unsigned int y = 0; y <= lastScanlinePair; ++y )
1192     {
1193       // Scale two scanlines horizontally:
1194       HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
1195       HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
1196
1197       // Scale vertical pairs of pixels while the last two scanlines are still warm in
1198       // the CPU cache(s):
1199       // Note, better access patterns for cache-coherence are possible for very large
1200       // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
1201       // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
1202       AverageScanlines(
1203           &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
1204           &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
1205           &pixels[y * scaledWidth * BYTES_PER_PIXEL],
1206           scaledWidth );
1207     }
1208   }
1209
1210   ///@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.
1211   outWidth = scaledWidth;
1212   outHeight = scaledHeight;
1213 }
1214
1215 }
1216
1217 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
1218 {
1219   DebugAssertScanlineParameters( pixels, width );
1220
1221   const unsigned int lastPair = EvenDown( width - 2 );
1222
1223   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1224   {
1225     // Load all the byte pixel components we need:
1226     const unsigned int c11 = pixels[pixel * 3];
1227     const unsigned int c12 = pixels[pixel * 3 + 1];
1228     const unsigned int c13 = pixels[pixel * 3 + 2];
1229     const unsigned int c21 = pixels[pixel * 3 + 3];
1230     const unsigned int c22 = pixels[pixel * 3 + 4];
1231     const unsigned int c23 = pixels[pixel * 3 + 5];
1232
1233     // Save the averaged byte pixel components:
1234     pixels[outPixel * 3]     = AverageComponent( c11, c21 );
1235     pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
1236     pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
1237   }
1238 }
1239
1240 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
1241 {
1242   DebugAssertScanlineParameters( pixels, width );
1243   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1244
1245   uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
1246
1247   const unsigned int lastPair = EvenDown( width - 2 );
1248
1249   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1250   {
1251     const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
1252     alignedPixels[outPixel] = averaged;
1253   }
1254 }
1255
1256 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
1257 {
1258   DebugAssertScanlineParameters( pixels, width );
1259   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1260
1261   uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
1262
1263   const unsigned int lastPair = EvenDown( width - 2 );
1264
1265   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1266   {
1267     const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
1268     alignedPixels[outPixel] = averaged;
1269   }
1270 }
1271
1272 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
1273 {
1274   DebugAssertScanlineParameters( pixels, width );
1275
1276   const unsigned int lastPair = EvenDown( width - 2 );
1277
1278   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1279   {
1280     // Load all the byte pixel components we need:
1281     const unsigned int c11 = pixels[pixel * 2];
1282     const unsigned int c12 = pixels[pixel * 2 + 1];
1283     const unsigned int c21 = pixels[pixel * 2 + 2];
1284     const unsigned int c22 = pixels[pixel * 2 + 3];
1285
1286     // Save the averaged byte pixel components:
1287     pixels[outPixel * 2]     = AverageComponent( c11, c21 );
1288     pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
1289   }
1290 }
1291
1292 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
1293 {
1294   DebugAssertScanlineParameters( pixels, width );
1295
1296   const unsigned int lastPair = EvenDown( width - 2 );
1297
1298   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
1299   {
1300     // Load all the byte pixel components we need:
1301     const unsigned int c1 = pixels[pixel];
1302     const unsigned int c2 = pixels[pixel + 1];
1303
1304     // Save the averaged byte pixel component:
1305     pixels[outPixel] = AverageComponent( c1, c2 );
1306   }
1307 }
1308
1309 /**
1310  * @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.
1311  * 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.
1312  */
1313 void AverageScanlines1( const unsigned char * const scanline1,
1314                         const unsigned char * const __restrict__ scanline2,
1315                         unsigned char* const outputScanline,
1316                         const unsigned int width )
1317 {
1318   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
1319
1320   for( unsigned int component = 0; component < width; ++component )
1321   {
1322     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1323   }
1324 }
1325
1326 void AverageScanlines2( const unsigned char * const scanline1,
1327                         const unsigned char * const __restrict__ scanline2,
1328                         unsigned char* const outputScanline,
1329                         const unsigned int width )
1330 {
1331   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1332
1333   for( unsigned int component = 0; component < width * 2; ++component )
1334   {
1335     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1336   }
1337 }
1338
1339 void AverageScanlines3( const unsigned char * const scanline1,
1340                         const unsigned char * const __restrict__ scanline2,
1341                         unsigned char* const outputScanline,
1342                         const unsigned int width )
1343 {
1344   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
1345
1346   for( unsigned int component = 0; component < width * 3; ++component )
1347   {
1348     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
1349   }
1350 }
1351
1352 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
1353                                const unsigned char * const __restrict__ scanline2,
1354                                unsigned char * const outputScanline,
1355                                const unsigned int width )
1356 {
1357   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
1358   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1359   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1360   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1361
1362   const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1363   const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1364   uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1365
1366   for( unsigned int pixel = 0; pixel < width; ++pixel )
1367   {
1368     alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
1369   }
1370 }
1371
1372 void AverageScanlinesRGB565( const unsigned char * const scanline1,
1373                              const unsigned char * const __restrict__ scanline2,
1374                              unsigned char * const outputScanline,
1375                              const unsigned int width )
1376 {
1377   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1378   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1379   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1380   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1381
1382   const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1383   const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1384   uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1385
1386   for( unsigned int pixel = 0; pixel < width; ++pixel )
1387   {
1388     alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
1389   }
1390 }
1391
1392 /// Dispatch to pixel format appropriate box filter downscaling functions.
1393 void DownscaleInPlacePow2( unsigned char * const pixels,
1394                            Pixel::Format pixelFormat,
1395                            unsigned int inputWidth,
1396                            unsigned int inputHeight,
1397                            unsigned int desiredWidth,
1398                            unsigned int desiredHeight,
1399                            FittingMode::Type fittingMode,
1400                            SamplingMode::Type samplingMode,
1401                            unsigned& outWidth,
1402                            unsigned& outHeight )
1403 {
1404   outWidth = inputWidth;
1405   outHeight = inputHeight;
1406   // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1407   if( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1408   {
1409     // Check the pixel format is one that is supported:
1410     if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1411     {
1412       const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
1413
1414       if( pixelFormat == Pixel::RGBA8888 )
1415       {
1416         Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1417       }
1418       else if( pixelFormat == Pixel::RGB888 )
1419       {
1420         Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1421       }
1422       else if( pixelFormat == Pixel::RGB565 )
1423       {
1424         Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1425       }
1426       else if( pixelFormat == Pixel::LA88 )
1427       {
1428         Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1429       }
1430       else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
1431       {
1432         Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1433       }
1434       else
1435       {
1436         DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1437       }
1438     }
1439   }
1440   else
1441   {
1442     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1443   }
1444 }
1445
1446 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
1447                                  unsigned int inputWidth,
1448                                  unsigned int inputHeight,
1449                                  unsigned int desiredWidth,
1450                                  unsigned int desiredHeight,
1451                                  BoxDimensionTest dimensionTest,
1452                                  unsigned& outWidth,
1453                                  unsigned& outHeight )
1454 {
1455   DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1456 }
1457
1458 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
1459                                    unsigned int inputWidth,
1460                                    unsigned int inputHeight,
1461                                    unsigned int desiredWidth,
1462                                    unsigned int desiredHeight,
1463                                    BoxDimensionTest dimensionTest,
1464                                    unsigned& outWidth,
1465                                    unsigned& outHeight )
1466 {
1467   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1468   DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1469 }
1470
1471 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
1472                                  unsigned int inputWidth,
1473                                  unsigned int inputHeight,
1474                                  unsigned int desiredWidth,
1475                                  unsigned int desiredHeight,
1476                                  BoxDimensionTest dimensionTest,
1477                                  unsigned int& outWidth,
1478                                  unsigned int& outHeight )
1479 {
1480   DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1481 }
1482
1483 /**
1484  * @copydoc DownscaleInPlacePow2RGB888
1485  *
1486  * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1487  */
1488 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
1489                                         unsigned int inputWidth,
1490                                         unsigned int inputHeight,
1491                                         unsigned int desiredWidth,
1492                                         unsigned int desiredHeight,
1493                                         BoxDimensionTest dimensionTest,
1494                                         unsigned& outWidth,
1495                                         unsigned& outHeight )
1496 {
1497   DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1498 }
1499
1500 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
1501                                              unsigned int inputWidth,
1502                                              unsigned int inputHeight,
1503                                              unsigned int desiredWidth,
1504                                              unsigned int desiredHeight,
1505                                              BoxDimensionTest dimensionTest,
1506                                              unsigned int& outWidth,
1507                                              unsigned int& outHeight )
1508 {
1509   DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1510 }
1511
1512 namespace
1513 {
1514
1515 /**
1516  * @brief Point sample an image to a new resolution (like GL_NEAREST).
1517  *
1518  * Template is used purely as a type-safe code generator in this one
1519  * compilation unit. Generated code is inlined into type-specific wrapper
1520  * functions below which are exported to rest of module.
1521  */
1522 template<typename PIXEL>
1523 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
1524                                    unsigned int inputWidth,
1525                                    unsigned int inputHeight,
1526                                    uint8_t * outPixels,
1527                                    unsigned int desiredWidth,
1528                                    unsigned int desiredHeight )
1529 {
1530   DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1531       outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1532       "The input and output buffers must not overlap for an upscaling.");
1533   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, ...)." );
1534   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, ...)." );
1535
1536   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1537   {
1538     return;
1539   }
1540   const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1541   PIXEL* const       outAligned = reinterpret_cast<PIXEL*>(outPixels);
1542   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
1543   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1544
1545   unsigned int inY = 0;
1546   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1547   {
1548     // Round fixed point y coordinate to nearest integer:
1549     const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1550     const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1551     PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1552
1553     DALI_ASSERT_DEBUG( integerY < inputHeight );
1554     DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1555     DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1556
1557     unsigned int inX = 0;
1558     for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1559     {
1560       // Round the fixed-point x coordinate to an integer:
1561       const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1562       const PIXEL* const inPixelAddress = &inScanline[integerX];
1563       const PIXEL pixel = *inPixelAddress;
1564       outScanline[outX] = pixel;
1565       inX += deltaX;
1566     }
1567     inY += deltaY;
1568   }
1569 }
1570
1571 }
1572
1573 // RGBA8888
1574 void PointSample4BPP( const unsigned char * inPixels,
1575                       unsigned int inputWidth,
1576                       unsigned int inputHeight,
1577                       unsigned char * outPixels,
1578                       unsigned int desiredWidth,
1579                       unsigned int desiredHeight )
1580 {
1581   PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1582 }
1583
1584 // RGB565, LA88
1585 void PointSample2BPP( const unsigned char * inPixels,
1586                       unsigned int inputWidth,
1587                       unsigned int inputHeight,
1588                       unsigned char * outPixels,
1589                       unsigned int desiredWidth,
1590                       unsigned int desiredHeight )
1591 {
1592   PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1593 }
1594
1595 // L8, A8
1596 void PointSample1BPP( const unsigned char * inPixels,
1597                       unsigned int inputWidth,
1598                       unsigned int inputHeight,
1599                       unsigned char * outPixels,
1600                       unsigned int desiredWidth,
1601                       unsigned int desiredHeight )
1602 {
1603   PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1604 }
1605
1606 /* RGB888
1607  * RGB888 is a special case as its pixels are not aligned addressable units.
1608  */
1609 void PointSample3BPP( const uint8_t * inPixels,
1610                       unsigned int inputWidth,
1611                       unsigned int inputHeight,
1612                       uint8_t * outPixels,
1613                       unsigned int desiredWidth,
1614                       unsigned int desiredHeight )
1615 {
1616   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1617   {
1618     return;
1619   }
1620   const unsigned int BYTES_PER_PIXEL = 3;
1621
1622   // Generate fixed-point 16.16 deltas in input image coordinates:
1623   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
1624   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1625
1626   // Step through output image in whole integer pixel steps while tracking the
1627   // corresponding locations in the input image using 16.16 fixed-point
1628   // coordinates:
1629   unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1630   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1631   {
1632     const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1633     const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1634     uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1635     unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1636
1637     for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1638     {
1639       // Round the fixed-point input coordinate to the address of the input pixel to sample:
1640       const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1641       const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1642
1643       // Issue loads for all pixel color components up-front:
1644       const unsigned int c0 = inPixelAddress[0];
1645       const unsigned int c1 = inPixelAddress[1];
1646       const unsigned int c2 = inPixelAddress[2];
1647       ///@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.
1648
1649       // Output the pixel components:
1650       outScanline[outX]     = c0;
1651       outScanline[outX + 1] = c1;
1652       outScanline[outX + 2] = c2;
1653
1654       // Increment the fixed-point input coordinate:
1655       inX += deltaX;
1656     }
1657
1658     inY += deltaY;
1659   }
1660 }
1661
1662 // Dispatch to a format-appropriate point sampling function:
1663 void PointSample( const unsigned char * inPixels,
1664                   unsigned int inputWidth,
1665                   unsigned int inputHeight,
1666                   Pixel::Format pixelFormat,
1667                   unsigned char * outPixels,
1668                   unsigned int desiredWidth,
1669                   unsigned int desiredHeight )
1670 {
1671   // Check the pixel format is one that is supported:
1672   if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1673   {
1674     if( pixelFormat == Pixel::RGB888 )
1675     {
1676       PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1677     }
1678     else if( pixelFormat == Pixel::RGBA8888 )
1679     {
1680       PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1681     }
1682     else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1683     {
1684       PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1685     }
1686     else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
1687     {
1688       PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1689     }
1690     else
1691     {
1692       DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1693     }
1694   }
1695   else
1696   {
1697     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1698   }
1699 }
1700
1701 // Linear sampling group below
1702
1703 namespace
1704 {
1705
1706 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1707 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1708 {
1709   return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1710 }
1711
1712 /** @copydoc BilinearFilter1BPPByte */
1713 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1714 {
1715   Pixel2Bytes pixel;
1716   pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
1717   pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1718   return pixel;
1719 }
1720
1721 /** @copydoc BilinearFilter1BPPByte */
1722 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1723 {
1724   Pixel3Bytes pixel;
1725   pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1726   pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1727   pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1728   return pixel;
1729 }
1730
1731 /** @copydoc BilinearFilter1BPPByte */
1732 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1733 {
1734   const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1735                             (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1736                              BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
1737   return pixel;
1738 }
1739
1740 /** @copydoc BilinearFilter1BPPByte */
1741 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1742 {
1743   Pixel4Bytes pixel;
1744   pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1745   pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1746   pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1747   pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1748   return pixel;
1749 }
1750
1751 /**
1752  * @brief Generic version of bilinear sampling image resize function.
1753  * @note Limited to one compilation unit and exposed through type-specific
1754  * wrapper functions below.
1755  */
1756 template<
1757   typename PIXEL,
1758   PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1759   bool DEBUG_ASSERT_ALIGNMENT
1760 >
1761 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1762                        ImageDimensions inputDimensions,
1763                        unsigned char * __restrict__ outPixels,
1764                        ImageDimensions desiredDimensions )
1765 {
1766   const unsigned int inputWidth = inputDimensions.GetWidth();
1767   const unsigned int inputHeight = inputDimensions.GetHeight();
1768   const unsigned int desiredWidth = desiredDimensions.GetWidth();
1769   const unsigned int desiredHeight = desiredDimensions.GetHeight();
1770
1771   DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth   * inputHeight   * sizeof(PIXEL)) ||
1772                       (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1773                      "Input and output buffers cannot overlap.");
1774   if( DEBUG_ASSERT_ALIGNMENT )
1775   {
1776     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, ...)." );
1777     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, ...)." );
1778   }
1779
1780   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1781   {
1782     return;
1783   }
1784   const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1785   PIXEL* const       outAligned = reinterpret_cast<PIXEL*>(outPixels);
1786   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
1787   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1788
1789   unsigned int inY = 0;
1790   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1791   {
1792     PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1793
1794     // Find the two scanlines to blend and the weight to blend with:
1795     const unsigned int integerY1 = inY >> 16u;
1796     const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1797     const unsigned int inputYWeight = inY & 65535u;
1798
1799     DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1800     DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1801
1802     const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1803     const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1804
1805     unsigned int inX = 0;
1806     for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1807     {
1808       // Work out the two pixel scanline offsets for this cluster of four samples:
1809       const unsigned int integerX1 = inX >> 16u;
1810       const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1811
1812       // Execute the loads:
1813       const PIXEL pixel1 = inScanline1[integerX1];
1814       const PIXEL pixel2 = inScanline2[integerX1];
1815       const PIXEL pixel3 = inScanline1[integerX2];
1816       const PIXEL pixel4 = inScanline2[integerX2];
1817       ///@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.
1818
1819       // Weighted bilinear filter:
1820       const unsigned int inputXWeight = inX & 65535u;
1821       outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1822
1823       inX += deltaX;
1824     }
1825     inY += deltaY;
1826   }
1827 }
1828
1829 }
1830
1831 // Format-specific linear scaling instantiations:
1832
1833 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1834                        ImageDimensions inputDimensions,
1835                        unsigned char * __restrict__ outPixels,
1836                        ImageDimensions desiredDimensions )
1837 {
1838   LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1839 }
1840
1841 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1842                        ImageDimensions inputDimensions,
1843                        unsigned char * __restrict__ outPixels,
1844                        ImageDimensions desiredDimensions )
1845 {
1846   LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1847 }
1848
1849 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1850                        ImageDimensions inputDimensions,
1851                        unsigned char * __restrict__ outPixels,
1852                        ImageDimensions desiredDimensions )
1853 {
1854   LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1855 }
1856
1857 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1858                        ImageDimensions inputDimensions,
1859                        unsigned char * __restrict__ outPixels,
1860                        ImageDimensions desiredDimensions )
1861 {
1862   LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1863 }
1864
1865 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1866                        ImageDimensions inputDimensions,
1867                        unsigned char * __restrict__ outPixels,
1868                        ImageDimensions desiredDimensions )
1869 {
1870   LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1871 }
1872
1873
1874 void Resample( const unsigned char * __restrict__ inPixels,
1875                ImageDimensions inputDimensions,
1876                unsigned char * __restrict__ outPixels,
1877                ImageDimensions desiredDimensions,
1878                Resampler::Filter filterType,
1879                int numChannels, bool hasAlpha )
1880 {
1881   // Got from the test.cpp of the ImageResampler lib.
1882   const float ONE_DIV_255 = 1.0f / 255.0f;
1883   const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
1884   const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
1885   const int ALPHA_CHANNEL = hasAlpha ? (numChannels-1) : 0;
1886
1887   static bool loadColorSpaces = true;
1888   static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
1889   static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
1890
1891   if( loadColorSpaces ) // Only create the color space conversions on the first execution
1892   {
1893     loadColorSpaces = false;
1894
1895     for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i )
1896     {
1897       srgbToLinear[i] = pow( static_cast<float>( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA );
1898     }
1899
1900     const float invLinearToSrgbTableSize = 1.0f / static_cast<float>( LINEAR_TO_SRGB_TABLE_SIZE );
1901     const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
1902
1903     for( int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i )
1904     {
1905       int k = static_cast<int>( 255.0f * pow( static_cast<float>( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f );
1906       if( k < 0 )
1907       {
1908         k = 0;
1909       }
1910       else if( k > MAX_UNSIGNED_CHAR )
1911       {
1912         k = MAX_UNSIGNED_CHAR;
1913       }
1914       linearToSrgb[i] = static_cast<unsigned char>( k );
1915     }
1916   }
1917
1918   std::vector<Resampler*> resamplers( numChannels );
1919   std::vector<Vector<float>> samples(numChannels);
1920
1921   const int srcWidth = inputDimensions.GetWidth();
1922   const int srcHeight = inputDimensions.GetHeight();
1923   const int dstWidth = desiredDimensions.GetWidth();
1924   const int dstHeight = desiredDimensions.GetHeight();
1925
1926   // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
1927   // used for the other components (a memory and slight cache efficiency optimization).
1928   resamplers[0] = new Resampler( srcWidth,
1929                                  srcHeight,
1930                                  dstWidth,
1931                                  dstHeight,
1932                                  Resampler::BOUNDARY_CLAMP,
1933                                  0.0f,           // sample_low,
1934                                  1.0f,           // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
1935                                  filterType,    // The type of filter.
1936                                  NULL,           // Pclist_x,
1937                                  NULL,           // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
1938                                  FILTER_SCALE,   // src_x_ofs,
1939                                  FILTER_SCALE ); // src_y_ofs. Offset input image by specified amount (fractional values okay).
1940   samples[0].Resize( srcWidth );
1941   for( int i = 1; i < numChannels; ++i )
1942   {
1943     resamplers[i] = new Resampler( srcWidth,
1944                                    srcHeight,
1945                                    dstWidth,
1946                                    dstHeight,
1947                                    Resampler::BOUNDARY_CLAMP,
1948                                    0.0f,
1949                                    1.0f,
1950                                    filterType,
1951                                    resamplers[0]->get_clist_x(),
1952                                    resamplers[0]->get_clist_y(),
1953                                    FILTER_SCALE,
1954                                    FILTER_SCALE );
1955     samples[i].Resize( srcWidth );
1956   }
1957
1958   const int srcPitch = srcWidth * numChannels;
1959   const int dstPitch = dstWidth * numChannels;
1960   int dstY = 0;
1961
1962   for( int srcY = 0; srcY < srcHeight; ++srcY )
1963   {
1964     const unsigned char* pSrc = &inPixels[srcY * srcPitch];
1965
1966     for( int x = 0; x < srcWidth; ++x )
1967     {
1968       for( int c = 0; c < numChannels; ++c )
1969       {
1970         if( c == ALPHA_CHANNEL && hasAlpha )
1971         {
1972           samples[c][x] = *pSrc++ * ONE_DIV_255;
1973         }
1974         else
1975         {
1976           samples[c][x] = srgbToLinear[*pSrc++];
1977         }
1978       }
1979     }
1980
1981     for( int c = 0; c < numChannels; ++c )
1982     {
1983       if( !resamplers[c]->put_line( &samples[c][0] ) )
1984       {
1985         DALI_ASSERT_DEBUG( !"Out of memory" );
1986       }
1987     }
1988
1989     for(;;)
1990     {
1991       int compIndex;
1992       for( compIndex = 0; compIndex < numChannels; ++compIndex )
1993       {
1994         const float* pOutputSamples = resamplers[compIndex]->get_line();
1995         if( !pOutputSamples )
1996         {
1997           break;
1998         }
1999
2000         const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL && hasAlpha );
2001         DALI_ASSERT_DEBUG( dstY < dstHeight );
2002         unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
2003
2004         for( int x = 0; x < dstWidth; ++x )
2005         {
2006           if( isAlphaChannel )
2007           {
2008             int c = static_cast<int>( 255.0f * pOutputSamples[x] + 0.5f );
2009             if( c < 0 )
2010             {
2011               c = 0;
2012             }
2013             else if( c > MAX_UNSIGNED_CHAR )
2014             {
2015               c = MAX_UNSIGNED_CHAR;
2016             }
2017             *pDst = static_cast<unsigned char>( c );
2018           }
2019           else
2020           {
2021             int j = static_cast<int>( LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f );
2022             if( j < 0 )
2023             {
2024               j = 0;
2025             }
2026             else if( j >= LINEAR_TO_SRGB_TABLE_SIZE )
2027             {
2028               j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
2029             }
2030             *pDst = linearToSrgb[j];
2031           }
2032
2033           pDst += numChannels;
2034         }
2035       }
2036       if( compIndex < numChannels )
2037       {
2038         break;
2039       }
2040
2041       ++dstY;
2042     }
2043   }
2044
2045   // Delete the resamplers.
2046   for( int i = 0; i < numChannels; ++i )
2047   {
2048     delete resamplers[i];
2049   }
2050 }
2051
2052 void LanczosSample4BPP( const unsigned char * __restrict__ inPixels,
2053                         ImageDimensions inputDimensions,
2054                         unsigned char * __restrict__ outPixels,
2055                         ImageDimensions desiredDimensions )
2056 {
2057   Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true );
2058 }
2059
2060 void LanczosSample1BPP( const unsigned char * __restrict__ inPixels,
2061                         ImageDimensions inputDimensions,
2062                         unsigned char * __restrict__ outPixels,
2063                         ImageDimensions desiredDimensions )
2064 {
2065   // For L8 images
2066   Resample( inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false );
2067 }
2068
2069 // Dispatch to a format-appropriate linear sampling function:
2070 void LinearSample( const unsigned char * __restrict__ inPixels,
2071                    ImageDimensions inDimensions,
2072                    Pixel::Format pixelFormat,
2073                    unsigned char * __restrict__ outPixels,
2074                    ImageDimensions outDimensions )
2075 {
2076   // Check the pixel format is one that is supported:
2077   if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
2078   {
2079     if( pixelFormat == Pixel::RGB888 )
2080     {
2081       LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
2082     }
2083     else if( pixelFormat == Pixel::RGBA8888 )
2084     {
2085       LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
2086     }
2087     else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
2088     {
2089       LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
2090     }
2091     else if( pixelFormat == Pixel::LA88 )
2092     {
2093       LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
2094     }
2095     else if ( pixelFormat == Pixel::RGB565 )
2096     {
2097       LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
2098     }
2099     else
2100     {
2101       DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
2102     }
2103   }
2104   else
2105   {
2106     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
2107   }
2108 }
2109
2110 void RotateByShear( const uint8_t* const pixelsIn,
2111                     unsigned int widthIn,
2112                     unsigned int heightIn,
2113                     unsigned int pixelSize,
2114                     float radians,
2115                     uint8_t*& pixelsOut,
2116                     unsigned int& widthOut,
2117                     unsigned int& heightOut )
2118 {
2119   // @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
2120
2121   // Do first the fast rotations to transform the angle into a (-45..45] range.
2122
2123   float fastRotationPerformed = false;
2124   if( ( radians > Math::PI_4 ) && ( radians <= RAD_135 ) )
2125   {
2126     // Angle in (45.0 .. 135.0]
2127     // Rotate image by 90 degrees into temporary image,
2128     // so it requires only an extra rotation angle
2129     // of -45.0 .. +45.0 to complete rotation.
2130     Rotate90( pixelsIn,
2131               widthIn,
2132               heightIn,
2133               pixelSize,
2134               pixelsOut,
2135               widthOut,
2136               heightOut );
2137     radians -= Math::PI_2;
2138     fastRotationPerformed = true;
2139   }
2140   else if( ( radians > RAD_135 ) && ( radians <= RAD_225 ) )
2141   {
2142     // Angle in (135.0 .. 225.0]
2143     // Rotate image by 180 degrees into temporary image,
2144     // so it requires only an extra rotation angle
2145     // of -45.0 .. +45.0 to complete rotation.
2146
2147     Rotate180( pixelsIn,
2148                widthIn,
2149                heightIn,
2150                pixelSize,
2151                pixelsOut );
2152     radians -= Math::PI;
2153     widthOut = widthIn;
2154     heightOut = heightIn;
2155     fastRotationPerformed = true;
2156   }
2157   else if( ( radians > RAD_225 ) && ( radians <= RAD_315 ) )
2158   {
2159     // Angle in (225.0 .. 315.0]
2160     // Rotate image by 270 degrees into temporary image,
2161     // so it requires only an extra rotation angle
2162     // of -45.0 .. +45.0 to complete rotation.
2163
2164     Rotate270( pixelsIn,
2165                widthIn,
2166                heightIn,
2167                pixelSize,
2168                pixelsOut,
2169                widthOut,
2170                heightOut );
2171     radians -= RAD_270;
2172     fastRotationPerformed = true;
2173   }
2174
2175   if( fabs( radians ) < Dali::Math::MACHINE_EPSILON_10 )
2176   {
2177     // Nothing else to do if the angle is zero.
2178     // The rotation angle was 90, 180 or 270.
2179
2180     // @note Allocated memory by 'Fast Rotations', if any, has to be freed by the called to this function.
2181     return;
2182   }
2183
2184   const uint8_t* const firstHorizontalSkwePixelsIn = fastRotationPerformed ? pixelsOut : pixelsIn;
2185   uint8_t*  tmpFirstHorizontalSkwePixelsIn = fastRotationPerformed ? pixelsOut : nullptr; // keep the pointer to free the memory.
2186
2187   // Reset the input/output
2188   widthIn = widthOut;
2189   heightIn = heightOut;
2190   pixelsOut = nullptr;
2191
2192   const float angleSinus = sin( radians );
2193   const float angleCosinus = cos( radians );
2194   const float angleTangent = tan( 0.5f * radians );
2195
2196   ///////////////////////////////////////
2197   // Perform 1st shear (horizontal)
2198   ///////////////////////////////////////
2199
2200   // Calculate first shear (horizontal) destination image dimensions
2201
2202   widthOut = widthIn + static_cast<unsigned int>( fabs( angleTangent ) * static_cast<float>( heightIn ) );
2203   heightOut = heightIn;
2204
2205   // Allocate the buffer for the 1st shear
2206   pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2207
2208   for( unsigned int y = 0u; y < heightOut; ++y )
2209   {
2210     const float shear = angleTangent * ( ( angleTangent >= 0.f ) ? ( 0.5f + static_cast<float>( y ) ) : ( 0.5f + static_cast<float>( y ) - static_cast<float>( heightOut ) ) );
2211
2212     const int intShear = static_cast<int>( floor( shear ) );
2213     HorizontalSkew( firstHorizontalSkwePixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>( intShear ) );
2214   }
2215
2216   // Free the memory allocated by the 'Fast Rotations'.
2217   free( tmpFirstHorizontalSkwePixelsIn );
2218
2219   uint8_t* tmpPixelsIn = pixelsOut;
2220   unsigned int tmpWidthIn = widthOut;
2221   unsigned int tmpHeightIn = heightOut;
2222
2223   // Reset the input/output
2224   pixelsOut = nullptr;
2225
2226   ///////////////////////////////////////
2227   // Perform 2nd shear (vertical)
2228   ///////////////////////////////////////
2229
2230   // Calc 2nd shear (vertical) destination image dimensions
2231   heightOut = static_cast<unsigned int>( static_cast<float>( widthIn ) * fabs( angleSinus ) + static_cast<float>( heightIn ) * angleCosinus );
2232
2233   // Allocate the buffer for the 2nd shear
2234   pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2235
2236   // Variable skew offset
2237   float offset = angleSinus * ( ( angleSinus > 0.f ) ? static_cast<float>( widthIn - 1u ) : -( static_cast<float>( widthIn ) - static_cast<float>( widthOut ) ) );
2238
2239   unsigned int column = 0u;
2240   for( column = 0u; column < widthOut; ++column, offset -= angleSinus )
2241   {
2242     const int shear = static_cast<int>( floor( offset ) );
2243     VerticalSkew( tmpPixelsIn, tmpWidthIn, tmpHeightIn, pixelSize, pixelsOut, widthOut, heightOut, column, shear, offset - static_cast<float>( shear ) );
2244   }
2245
2246   // Free the memory allocated by the 'First Horizontal Skew'.
2247   free( tmpPixelsIn );
2248
2249   // Reset the input/output
2250   tmpPixelsIn = pixelsOut;
2251   tmpWidthIn = widthOut;
2252   tmpHeightIn = heightOut;
2253   pixelsOut = nullptr;
2254
2255   ///////////////////////////////////////
2256   // Perform 3rd shear (horizontal)
2257   ///////////////////////////////////////
2258
2259   // Calc 3rd shear (horizontal) destination image dimensions
2260   widthOut = static_cast<unsigned int>( static_cast<float>( heightIn ) * fabs( angleSinus ) + static_cast<float>( widthIn ) * angleCosinus ) + 1u;
2261
2262   // Allocate the buffer for the 3rd shear
2263   pixelsOut = static_cast<uint8_t*>( malloc( widthOut * heightOut * pixelSize ) );
2264
2265   offset =  ( angleSinus >= 0.f ) ? -angleSinus * angleTangent * static_cast<float>( widthIn - 1u ) : angleTangent * ( static_cast<float>( widthIn - 1u ) * -angleSinus + ( 1.f - static_cast<float>( heightOut ) ) );
2266
2267   for( unsigned int y = 0u; y < heightOut; ++y, offset += angleTangent )
2268   {
2269     const int shear = static_cast<int>( floor( offset ) );
2270     HorizontalSkew( tmpPixelsIn, tmpWidthIn, pixelSize, pixelsOut, widthOut, y, shear, offset - static_cast<float>( shear ) );
2271   }
2272
2273   // Free the memory allocated by the 'First Horizontal Skew'.
2274   free( tmpPixelsIn );
2275
2276   // @note Allocated memory by the last 'Horizontal Skew' has to be freed by the called to this function.
2277 }
2278
2279 } /* namespace Platform */
2280 } /* namespace Internal */
2281 } /* namespace Dali */