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