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