Image scaling operations - FilterMode::Box implemented for all pixel formats
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / portable / image-operations.cpp
1 /*
2  * Copyright (c) 2014 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 // INTERNAL INCLUDES
19 #include "image-operations.h"
20 #include <dali/integration-api/debug.h>
21 #include <dali/public-api/common/ref-counted-dali-vector.h>
22 #include <dali/public-api/images/image-attributes.h>
23 #include <dali/integration-api/bitmap.h>
24
25 // EXTERNAL INCLUDES
26 #include <cstring>
27
28 namespace Dali
29 {
30 namespace Internal
31 {
32 namespace Platform
33 {
34
35 namespace
36 {
37 using Integration::Bitmap;
38 using Integration::BitmapPtr;
39 typedef unsigned char PixelBuffer;
40
41 #if defined(DEBUG_ENABLED)
42 /**
43  * Disable logging of image operations or make it verbose from the commandline
44  * as follows (e.g., for dali demo app):
45  * <code>
46  * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
47  * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
48  * </code>
49  */
50 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
51 #endif
52
53 /** @return The greatest even number less than or equal to the argument. */
54 inline unsigned int EvenDown( const unsigned int a )
55 {
56   const unsigned int evened = a & ~1u;
57   return evened;
58 }
59
60 /**
61  * @brief Log bad parameters.
62  */
63 void ValidateScalingParameters(
64   const unsigned int inputWidth,    const unsigned int inputHeight,
65   const unsigned int desiredWidth, const unsigned int desiredHeight )
66 {
67   if( desiredWidth > inputWidth || desiredHeight > inputHeight )
68   {
69     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
70   }
71
72   if( desiredWidth == 0u || desiredHeight == 0u )
73   {
74     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
75   }
76
77   if( inputWidth == 0u || inputHeight == 0u )
78   {
79     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
80   }
81 }
82
83 /**
84  * @brief Do debug assertions common to all scanline halving functions.
85  * @note Inline and in anon namespace so should boil away in release builds.
86  */
87 inline void DebugAssertScanlineParameters(
88     unsigned char * const pixels,
89     const unsigned int width )
90 {
91   DALI_ASSERT_DEBUG( pixels && "Null pointer." );
92   DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
93   DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
94 }
95
96 /**
97  * @brief Assertions on params to functions averaging pairs of scanlines.
98  */
99 inline void DebugAssertDualScanlineParameters(
100     const unsigned char * const scanline1,
101     const unsigned char * const scanline2,
102     unsigned char* const outputScanline,
103     const size_t widthInComponents )
104 {
105   DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
106   DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
107   DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
108   DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
109   DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
110 }
111
112 } // namespace - unnamed
113
114 /**
115  * @brief Implement ImageAttributes::ScaleTofill scaling mode.
116  *
117  * Implement the ImageAttributes::ScaleToFill mode, returning a new bitmap with the aspect ratio specified by the scaling mode.
118  * @note This fakes the scaling with a crop and relies on the GPU scaling at
119  * render time. If the input bitmap was previously maximally downscaled using a
120  * repeated box filter, this is a reasonable approach.
121  * @return The bitmap passed in if no scaling is needed or possible, else a new,
122  * smaller bitmap with the scaling mode applied.
123  */
124 Integration::BitmapPtr ProcessBitmapScaleToFill( Integration::BitmapPtr bitmap, const ImageAttributes& requestedAttributes );
125
126
127 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
128 {
129   // If a different size than the raw one has been requested, resize the image
130   // maximally using a repeated box filter without making it smaller than the
131   // requested size in either dimension:
132   if( bitmap )
133   {
134     bitmap = DownscaleBitmap( *bitmap, requestedAttributes );
135   }
136
137   // Cut the bitmap according to the desired width and height so that the
138   // resulting bitmap has the same aspect ratio as the desired dimensions:
139   if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
140   {
141     bitmap = ProcessBitmapScaleToFill( bitmap, requestedAttributes );
142   }
143
144   // Examine the image pixels remaining after cropping and scaling to see if all
145   // are opaque, allowing faster rendering, or some have non-1.0 alpha:
146   if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
147   {
148     bitmap->GetPackedPixelsProfile()->TestForTransparency();
149   }
150   return bitmap;
151 }
152
153 BitmapPtr ProcessBitmapScaleToFill( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
154 {
155   const unsigned loadedWidth = bitmap->GetImageWidth();
156   const unsigned loadedHeight = bitmap->GetImageHeight();
157   const unsigned desiredWidth = requestedAttributes.GetWidth();
158   const unsigned desiredHeight = requestedAttributes.GetHeight();
159
160   if( desiredWidth < 1U || desiredHeight < 1U )
161   {
162     DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
163   }
164   else if( loadedWidth != desiredWidth || loadedHeight != desiredHeight )
165   {
166     const Vector2 desiredDims( desiredWidth, desiredHeight );
167
168     // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
169     // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
170     const float widthsRatio = loadedWidth / float(desiredWidth);
171     const Vector2 scaledByWidth = desiredDims * widthsRatio;
172     const float heightsRatio = loadedHeight / float(desiredHeight);
173     const Vector2 scaledByHeight = desiredDims * heightsRatio;
174     // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
175     const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
176     const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
177
178     // Work out how many pixels to trim from top and bottom, and left and right:
179     // (We only ever do one dimension)
180     const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - loadedHeight) * 0.5f ) : 0;
181     const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - loadedWidth) * 0.5f );
182
183     DALI_LOG_INFO( gImageOpsLogFilter, Debug::Concise, "Bitmap, desired(%f, %f), loaded(%u,%u), cut_target(%f, %f), trimmed(%u, %u), vertical = %s.\n", desiredDims.x, desiredDims.y, loadedWidth, loadedHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" );
184
185     // Make a new bitmap with the central part of the loaded one if required:
186     if( scanlinesToTrim > 0 || columnsToTrim > 0 )
187     {
188       const unsigned newWidth = loadedWidth - 2 * columnsToTrim;
189       const unsigned newHeight = loadedHeight - 2 * scanlinesToTrim;
190       BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
191       Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
192       DALI_ASSERT_DEBUG( packedView );
193       const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
194       packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
195
196       const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
197
198       const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * loadedWidth * bytesPerPixel;
199       PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
200       DALI_ASSERT_DEBUG( srcPixels && destPixels );
201
202       // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
203       if( trimTopAndBottom )
204       {
205         memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
206       }
207       else
208       {
209         for( unsigned y = 0; y < newHeight; ++y )
210         {
211           memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * loadedWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
212         }
213       }
214
215       // Overwrite the loaded bitmap with the cropped version:
216       bitmap = croppedBitmap;
217     }
218   }
219
220   return bitmap;
221 }
222
223 namespace
224 {
225
226 /**
227  * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
228  */
229 BoxDimensionTest DimensionTestForScalingMode( ImageAttributes::ScalingMode scalingMode )
230 {
231   BoxDimensionTest dimensionTest;
232
233   switch( scalingMode )
234   {
235     // Shrink to fit attempts to make one or zero dimensions smaller than the
236     // desired dimensions and one or two dimensions exactly the same as the desired
237     // ones, so as long as one dimension is larger than the desired size, box
238     // filtering can continue even if the second dimension is smaller than the
239     // desired dimensions:
240     case ImageAttributes::ShrinkToFit:
241       dimensionTest = BoxDimensionTestEither;
242       break;
243     // Scale to fill mode keeps both dimensions at least as large as desired:
244     case ImageAttributes::ScaleToFill:
245       dimensionTest = BoxDimensionTestBoth;
246       break;
247     // Y dimension is irrelevant when downscaling in FitWidth mode:
248     case ImageAttributes::FitWidth:
249       dimensionTest = BoxDimensionTestX;
250       break;
251     // X Dimension is ignored by definition in FitHeight mode:
252     case ImageAttributes::FitHeight:
253       dimensionTest = BoxDimensionTestY;
254   }
255
256   return dimensionTest;
257 }
258
259 }
260
261 // The top-level function to return a downscaled version of a bitmap:
262 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap, const ImageAttributes& requestedAttributes )
263 {
264   const unsigned int bitmapWidth  = bitmap.GetImageWidth();
265   const unsigned int bitmapHeight = bitmap.GetImageHeight();
266   const Size requestedSize = requestedAttributes.GetSize();
267
268   // If a different size than the raw one has been requested, resize the image:
269   if( bitmap.GetPackedPixelsProfile() &&
270       (requestedSize.x > 0.0f) && (requestedSize.y > 0.0f) &&
271       (requestedSize.x < bitmapWidth) &&
272       (requestedSize.y < bitmapHeight) )
273   {
274     const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
275     const ImageAttributes::ScalingMode scalingMode = requestedAttributes.GetScalingMode();
276     const ImageAttributes::FilterMode filterMode = requestedAttributes.GetFilterMode();
277
278     // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
279     if( filterMode == ImageAttributes::Box || filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear )
280     {
281       // Check the pixel format is one that is supported:
282       if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
283       {
284         unsigned int shrunkWidth = -1, shrunkHeight = -1;
285         const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( scalingMode );
286
287         if( pixelFormat == Pixel::RGBA8888 )
288         {
289           Internal::Platform::DownscaleInPlacePow2RGBA8888( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
290         }
291         else if( pixelFormat == Pixel::RGB888 )
292         {
293           Internal::Platform::DownscaleInPlacePow2RGB888( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
294         }
295         else if( pixelFormat == Pixel::RGB565 )
296         {
297           Internal::Platform::DownscaleInPlacePow2RGB565( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
298         }
299         else if( pixelFormat == Pixel::LA88 )
300         {
301           Internal::Platform::DownscaleInPlacePow2ComponentPair( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
302         }
303         else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
304         {
305           Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( bitmap.GetBuffer(), bitmapWidth, bitmapHeight, requestedSize.x, requestedSize.y, dimensionTest, shrunkWidth, shrunkHeight );
306         }
307
308         if( shrunkWidth != bitmapWidth && shrunkHeight != bitmapHeight )
309         {
310           // Allocate a pixel buffer to hold the shrunk image:
311           Integration::BitmapPtr shrunk = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
312           shrunk->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, shrunkWidth, shrunkHeight, shrunkWidth, shrunkHeight );
313
314           // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
315           DALI_ASSERT_DEBUG( bitmap.GetBuffer() && "Null loaded bitmap buffer." );
316           DALI_ASSERT_DEBUG( shrunk->GetBuffer() && "Null shrunk bitmap buffer." );
317           memcpy( shrunk->GetBuffer(), bitmap.GetBuffer(), shrunkWidth * shrunkHeight * Pixel::GetBytesPerPixel( pixelFormat ) );
318           return shrunk;
319         }
320       }
321       else
322       {
323         DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
324       }
325     }
326   }
327   return Integration::BitmapPtr(&bitmap);
328 }
329
330 namespace
331 {
332 /**
333  * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
334  * @param test Which combination of the two dimensions matter for terminating the filtering.
335  * @param scaledWidth The width of the current downscaled image.
336  * @param scaledHeight The height of the current downscaled image.
337  * @param desiredWidth The target width for the downscaling.
338  * @param desiredHeight The target height for the downscaling.
339  */
340 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
341 {
342   bool keepScaling = false;
343   const unsigned int nextWidth = scaledWidth >> 1u;
344   const unsigned int nextHeight = scaledHeight >> 1u;
345
346   if( nextWidth >= 1u && nextHeight >= 1u )
347   {
348     switch( test )
349     {
350       case BoxDimensionTestEither:
351         keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
352         break;
353       case BoxDimensionTestBoth:
354         keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
355         break;
356       case BoxDimensionTestX:
357         keepScaling = nextWidth >= desiredWidth;
358         break;
359       case BoxDimensionTestY:
360         keepScaling = nextHeight >= desiredHeight;
361     }
362   }
363
364   return keepScaling;
365 }
366
367 /**
368  * @brief A shared implementation of the overall iterative downscaling algorithm.
369  *
370  * Specialise this for particular pixel formats by supplying the number of bytes
371  * per pixel and two functions: one for averaging pairs of neighbouring pixels
372  * on a single scanline, and a second for averaging pixels at corresponding
373  * positions on different scanlines.
374  **/
375 template<
376   int BYTES_PER_PIXEL,
377   void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
378   void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
379 >
380 void DownscaleInPlacePow2Generic(
381     unsigned char * const pixels,
382     const unsigned int inputWidth, const unsigned int inputHeight,
383     const unsigned int desiredWidth, const unsigned int desiredHeight,
384     BoxDimensionTest dimensionTest,
385     unsigned& outWidth, unsigned& outHeight )
386 {
387   if( pixels == 0 )
388   {
389     return;
390   }
391   ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
392
393   // Scale the image until it would be smaller than desired, stopping if the
394   // resulting height or width would be less than 1:
395   unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
396   while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
397       //scaledWidth >> 1u >= desiredWidth && scaledHeight >> 1u >= desiredHeight &&
398       //    scaledWidth >> 1u >= 1u           && scaledHeight >> 1u >= 1u )
399   {
400     const unsigned int lastWidth = scaledWidth;
401     scaledWidth  >>= 1u;
402     scaledHeight >>= 1u;
403
404     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
405
406     const unsigned int lastScanlinePair = scaledHeight - 1;
407
408     // Scale pairs of scanlines until any spare one at the end is dropped:
409     for( unsigned int y = 0; y <= lastScanlinePair; ++y )
410     {
411       // Scale two scanlines horizontally:
412       HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
413       HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
414
415       // Scale vertical pairs of pixels while the last two scanlines are still warm in
416       // the CPU cache(s):
417       // Note, better access patterns for cache-coherence are possible for very large
418       // images but even a 4k RGB888 image will use just 24kB of cache (4k pixels
419       // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
420       AverageScanlines(
421           &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
422           &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
423           &pixels[y * scaledWidth * BYTES_PER_PIXEL],
424           scaledWidth );
425     }
426   }
427
428   ///@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.
429   outWidth = scaledWidth;
430   outHeight = scaledHeight;
431 }
432
433 }
434
435 void HalveScanlineInPlaceRGB888(
436     unsigned char * const pixels,
437     const unsigned int width )
438 {
439   DebugAssertScanlineParameters( pixels, width );
440
441   const unsigned int lastPair = EvenDown( width - 2 );
442
443   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
444   {
445     // Load all the byte pixel components we need:
446     const unsigned int c11 = pixels[pixel * 3];
447     const unsigned int c12 = pixels[pixel * 3 + 1];
448     const unsigned int c13 = pixels[pixel * 3 + 2];
449     const unsigned int c21 = pixels[pixel * 3 + 3];
450     const unsigned int c22 = pixels[pixel * 3 + 4];
451     const unsigned int c23 = pixels[pixel * 3 + 5];
452
453     // Save the averaged byte pixel components:
454     pixels[outPixel * 3]     = AverageComponent( c11, c21 );
455     pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
456     pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
457   }
458 }
459
460 void HalveScanlineInPlaceRGBA8888(
461     unsigned char * const pixels,
462     const unsigned int width )
463 {
464   DebugAssertScanlineParameters( pixels, width );
465   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
466
467   uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
468
469   const unsigned int lastPair = EvenDown( width - 2 );
470
471   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
472   {
473     const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
474     alignedPixels[outPixel] = averaged;
475   }
476 }
477
478 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
479 {
480   DebugAssertScanlineParameters( pixels, width );
481   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
482
483   uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
484
485   const unsigned int lastPair = EvenDown( width - 2 );
486
487   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
488   {
489     const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
490     alignedPixels[outPixel] = averaged;
491   }
492 }
493
494 void HalveScanlineInPlace2Bytes(
495     unsigned char * const pixels,
496     const unsigned int width )
497 {
498   DebugAssertScanlineParameters( pixels, width );
499
500   const unsigned int lastPair = EvenDown( width - 2 );
501
502   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
503   {
504     // Load all the byte pixel components we need:
505     const unsigned int c11 = pixels[pixel * 2];
506     const unsigned int c12 = pixels[pixel * 2 + 1];
507     const unsigned int c21 = pixels[pixel * 2 + 2];
508     const unsigned int c22 = pixels[pixel * 2 + 3];
509
510     // Save the averaged byte pixel components:
511     pixels[outPixel * 2]     = AverageComponent( c11, c21 );
512     pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
513   }
514 }
515
516 void HalveScanlineInPlace1Byte(
517     unsigned char * const pixels,
518     const unsigned int width )
519 {
520   DebugAssertScanlineParameters( pixels, width );
521
522   const unsigned int lastPair = EvenDown( width - 2 );
523
524   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
525   {
526     // Load all the byte pixel components we need:
527     const unsigned int c1 = pixels[pixel];
528     const unsigned int c2 = pixels[pixel + 1];
529
530     // Save the averaged byte pixel component:
531     pixels[outPixel] = AverageComponent( c1, c2 );
532   }
533 }
534
535 /**
536  * @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.
537  * 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.
538  */
539 void AverageScanlines1(
540     const unsigned char * const scanline1,
541     const unsigned char * const __restrict__ scanline2,
542     unsigned char* const outputScanline,
543     const unsigned int width )
544 {
545   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
546
547   for( unsigned int component = 0; component < width; ++component )
548   {
549     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
550   }
551 }
552
553 void AverageScanlines2(
554     const unsigned char * const scanline1,
555     const unsigned char * const __restrict__ scanline2,
556     unsigned char* const outputScanline,
557     const unsigned int width )
558 {
559   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
560
561   for( unsigned int component = 0; component < width * 2; ++component )
562   {
563     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
564   }
565 }
566
567 void AverageScanlines3(
568     const unsigned char * const scanline1,
569     const unsigned char * const __restrict__ scanline2,
570     unsigned char* const outputScanline,
571     const unsigned int width )
572 {
573   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
574
575   for( unsigned int component = 0; component < width * 3; ++component )
576   {
577     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
578   }
579 }
580
581 void AverageScanlinesRGBA8888(
582     const unsigned char * const scanline1,
583     const unsigned char * const __restrict__ scanline2,
584     unsigned char * const outputScanline,
585     const unsigned int width )
586 {
587   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
588   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
589   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
590   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
591
592   const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
593   const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
594   uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
595
596   for( unsigned int pixel = 0; pixel < width; ++pixel )
597   {
598     alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
599   }
600 }
601
602 void AverageScanlinesRGB565(
603     const unsigned char * const scanline1,
604     const unsigned char * const __restrict__ scanline2,
605     unsigned char * const outputScanline,
606     const unsigned int width )
607 {
608   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
609   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
610   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
611   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
612
613   const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
614   const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
615   uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
616
617   for( unsigned int pixel = 0; pixel < width; ++pixel )
618   {
619     alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
620   }
621 }
622
623 void DownscaleInPlacePow2RGB888(
624     unsigned char * const pixels,
625     const unsigned int inputWidth, const unsigned int inputHeight,
626     const unsigned int desiredWidth, const unsigned int desiredHeight,
627     BoxDimensionTest dimensionTest,
628     unsigned& outWidth, unsigned& outHeight )
629 {
630   DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
631 }
632
633 void DownscaleInPlacePow2RGBA8888(
634     unsigned char * const pixels,
635     const unsigned int inputWidth, const unsigned int inputHeight,
636     const unsigned int desiredWidth, const unsigned int desiredHeight,
637     BoxDimensionTest dimensionTest,
638     unsigned& outWidth, unsigned& outHeight )
639 {
640   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
641   DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
642 }
643
644 void DownscaleInPlacePow2RGB565(
645     unsigned char * pixels,
646     unsigned int inputWidth, unsigned int inputHeight,
647     unsigned int desiredWidth, unsigned int desiredHeight,
648     BoxDimensionTest dimensionTest,
649     unsigned int& outWidth, unsigned int& outHeight )
650 {
651   DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
652 }
653
654 /**
655  * @copydoc DownscaleInPlacePow2RGB888
656  *
657  * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
658  */
659 void DownscaleInPlacePow2ComponentPair(
660     unsigned char * const pixels,
661     const unsigned int inputWidth, const unsigned int inputHeight,
662     const unsigned int desiredWidth, const unsigned int desiredHeight,
663     BoxDimensionTest dimensionTest,
664     unsigned& outWidth, unsigned& outHeight )
665 {
666   DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
667 }
668
669 void DownscaleInPlacePow2SingleBytePerPixel(
670     unsigned char * pixels,
671     unsigned int inputWidth, unsigned int inputHeight,
672     unsigned int desiredWidth, unsigned int desiredHeight,
673     BoxDimensionTest dimensionTest,
674     unsigned int& outWidth, unsigned int& outHeight )
675 {
676   DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
677 }
678
679 } /* namespace Platform */
680 } /* namespace Internal */
681 } /* namespace Dali */