Merge "Make a full screen window in the mobile emulator which has an HD screen" into...
[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 #include "image-operations.h"
19
20 // EXTERNAL INCLUDES
21 #include <cstring>
22 #include <stddef.h>
23 #include <dali/integration-api/debug.h>
24
25 // INTERNAL INCLUDES
26
27 namespace Dali
28 {
29 namespace Internal
30 {
31 namespace Platform
32 {
33
34 namespace
35 {
36 using Integration::Bitmap;
37 using Integration::BitmapPtr;
38 typedef unsigned char PixelBuffer;
39
40 #if defined(DEBUG_ENABLED)
41 /**
42  * Disable logging of image operations or make it verbose from the commandline
43  * as follows (e.g., for dali demo app):
44  * <code>
45  * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
46  * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
47  * </code>
48  */
49 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
50 #endif
51
52 /** @return The greatest even number less than or equal to the argument. */
53 inline unsigned int EvenDown( const unsigned int a )
54 {
55   const unsigned int evened = a & ~1u;
56   return evened;
57 }
58
59 /**
60  * @brief Log bad parameters.
61  */
62 void ValidateScalingParameters( const unsigned int inputWidth,
63                                 const unsigned int inputHeight,
64                                 const unsigned int desiredWidth,
65                                 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( const uint8_t * const pixels, const unsigned int width )
88 {
89   DALI_ASSERT_DEBUG( pixels && "Null pointer." );
90   DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
91   DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
92 }
93
94 /**
95  * @brief Assertions on params to functions averaging pairs of scanlines.
96  * @note Inline as intended to boil away in release.
97  */
98 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
99                                                const uint8_t * const scanline2,
100                                                uint8_t* const outputScanline,
101                                                const size_t widthInComponents )
102 {
103   DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
104   DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
105   DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
106   DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
107   DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
108 }
109
110 /**
111  * @brief Work out the desired width and height according to the rules documented for the ImageAttributes class.
112  *
113  * @param[in] bitmapWidth Width of image before processing.
114  * @param[in] bitmapHeight Height of image before processing.
115  * @param[in] requestedWidth Width of area to scale image into. Can be zero.
116  * @param[in] requestedHeight Height of area to scale image into. Can be zero.
117  * @return Dimensions of area to scale image into after special rules are applied.
118  * @see ImageAttributes
119  */
120 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
121 {
122   // If no dimensions have been requested, default to the source ones:
123   if( requestedWidth == 0 && requestedHeight == 0 )
124   {
125     return ImageDimensions( bitmapWidth, bitmapHeight );
126   }
127
128   // If both dimensions have values requested, use them both:
129   if( requestedWidth != 0 && requestedHeight != 0 )
130   {
131     return ImageDimensions( requestedWidth, requestedHeight );
132   }
133
134   // If only one of the dimensions has been requested, calculate the other from
135   // the requested one and the source image aspect ratio:
136
137   if( requestedWidth != 0 )
138   {
139     return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
140   }
141   return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
142 }
143
144 /**
145  * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
146  */
147 BoxDimensionTest DimensionTestForScalingMode( ImageAttributes::ScalingMode scalingMode )
148 {
149   BoxDimensionTest dimensionTest;
150   dimensionTest = BoxDimensionTestEither;
151
152   switch( scalingMode )
153   {
154     // Shrink to fit attempts to make one or zero dimensions smaller than the
155     // desired dimensions and one or two dimensions exactly the same as the desired
156     // ones, so as long as one dimension is larger than the desired size, box
157     // filtering can continue even if the second dimension is smaller than the
158     // desired dimensions:
159     case ImageAttributes::ShrinkToFit:
160     {
161       dimensionTest = BoxDimensionTestEither;
162       break;
163     }
164     // Scale to fill mode keeps both dimensions at least as large as desired:
165     case ImageAttributes::ScaleToFill:
166     {
167       dimensionTest = BoxDimensionTestBoth;
168       break;
169     }
170     // Y dimension is irrelevant when downscaling in FitWidth mode:
171     case ImageAttributes::FitWidth:
172     {
173       dimensionTest = BoxDimensionTestX;
174       break;
175     }
176     // X Dimension is ignored by definition in FitHeight mode:
177     case ImageAttributes::FitHeight:
178     {
179       dimensionTest = BoxDimensionTestY;
180       break;
181     }
182   }
183
184   return dimensionTest;
185 }
186
187 /**
188  * @brief Work out the dimensions for a uniform scaling of the input to map it
189  * into the target while effecting ShinkToFit scaling mode.
190  */
191 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
192 {
193   DALI_ASSERT_DEBUG( true && " " );
194   // Scale the input by the least extreme of the two dimensions:
195   const float widthScale  = target.GetX() / float(source.GetX());
196   const float heightScale = target.GetY() / float(source.GetY());
197   const float scale = widthScale < heightScale ? widthScale : heightScale;
198
199   // Do no scaling at all if the result would increase area:
200   if( scale >= 1.0f )
201   {
202     return source;
203   }
204
205   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
206 }
207
208 /**
209  * @brief Work out the dimensions for a uniform scaling of the input to map it
210  * into the target while effecting ScaleToFill scaling mode.
211  * @note The output dimensions will need either top and bottom or left and right
212  * to be cropped away unless the source was pre-cropped to match the destination
213  * aspect ratio.
214  */
215 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
216 {
217   DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0  && "Zero-area rectangles should not be passed-in" );
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 FitWidth scaling mode.
235  */
236 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
237 {
238   DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
239   const float scale  = target.GetX() / float(source.GetX());
240
241   // Do no scaling at all if the result would increase area:
242   if( scale >= 1.0f )
243   {
244    return source;
245   }
246   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
247 }
248
249 /**
250  * @brief Work out the dimensions for a uniform scaling of the input to map it
251  * into the target while effecting FitHeight scaling mode.
252  */
253 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
254 {
255   DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
256   const float scale = target.GetY() / float(source.GetY());
257
258   // Do no scaling at all if the result would increase area:
259   if( scale >= 1.0f )
260   {
261     return source;
262   }
263
264   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
265 }
266
267 /**
268  * @brief Generate the rectangle to use as the target of a pixel sampling pass
269  * (e.g., nearest or linear).
270  */
271 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, ImageAttributes::ScalingMode scalingMode )
272 {
273   ImageDimensions fitDimensions;
274   switch( scalingMode )
275   {
276     case ImageAttributes::ShrinkToFit:
277     {
278       fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
279       break;
280     }
281     case ImageAttributes::ScaleToFill:
282     {
283       fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
284       break;
285     }
286     case ImageAttributes::FitWidth:
287     {
288       fitDimensions = FitForFitWidth( requestedSize, sourceSize );
289       break;
290     }
291     case ImageAttributes::FitHeight:
292     {
293       fitDimensions = FitForFitHeight( requestedSize, sourceSize );
294       break;
295     }
296   };
297
298   return fitDimensions;
299 }
300
301 /**
302  * @brief Construct a bitmap object from a copy of the pixel array passed in.
303  */
304 BitmapPtr MakeBitmap(const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
305 {
306   DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
307   DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
308
309   // Allocate a pixel buffer to hold the image passed in:
310   Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
311   newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
312
313   // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
314   memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
315   return newBitmap;
316 }
317
318 } // namespace - unnamed
319
320 /**
321  * @brief Implement ImageAttributes::ScaleTofill scaling mode cropping.
322  *
323  * Implement the cropping required for ImageAttributes::ScaleToFill mode,
324  * returning a new bitmap with the aspect ratio specified by the scaling mode.
325  * This scaling mode selects the central portion of a source image so any spare
326  * pixels off one of either the top or bottom edge need to be removed.
327  *
328  * @note If the input bitmap was not previously downscaled to exactly encompass
329  * the desired output size, the resulting bitmap will have the correct aspect
330  * ratio but will have larger dimensions than requested. This can be used to
331  * fake the scaling mode by relying on the GPU scaling at render time.
332  * If the input bitmap was previously maximally downscaled using a
333  * repeated box filter, this is a reasonable approach.
334  *
335  * @return The bitmap passed in if no scaling is needed or possible, else a new,
336  * smaller bitmap with the cropping required for the scaling mode applied.
337  */
338 Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
339
340 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
341 {
342   ///@ToDo: Optimisation - If Scaling Mode is ScaletoFill, either do cropping here at the front of the pipe, or equivalently modify all scaling functions to take a source rectangle and have the first one to be applied, pull in a subset of source pixels to crop on the fly. That would make every scaling slightly slower but would save the memcpy()s at the end for ScaleToFill.
343
344   if( bitmap )
345   {
346     // Calculate the desired box, accounting for a possible zero component:
347     const ImageDimensions desiredDimensions  = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), requestedAttributes.GetWidth(), requestedAttributes.GetHeight() );
348
349     // If a different size than the raw one has been requested, resize the image
350     // maximally using a repeated box filter without making it smaller than the
351     // requested size in either dimension:
352     bitmap = DownscaleBitmap( *bitmap, desiredDimensions, requestedAttributes.GetScalingMode(), requestedAttributes.GetFilterMode() );
353
354     // Cut the bitmap according to the desired width and height so that the
355     // resulting bitmap has the same aspect ratio as the desired dimensions:
356     if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
357     {
358       bitmap = CropForScaleToFill( bitmap, desiredDimensions );
359     }
360
361     // Examine the image pixels remaining after cropping and scaling to see if all
362     // are opaque, allowing faster rendering, or some have non-1.0 alpha:
363     if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
364     {
365       bitmap->GetPackedPixelsProfile()->TestForTransparency();
366     }
367   }
368
369   return bitmap;
370 }
371
372 BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
373 {
374   const unsigned inputWidth = bitmap->GetImageWidth();
375   const unsigned inputHeight = bitmap->GetImageHeight();
376   const unsigned desiredWidth = desiredDimensions.GetWidth();
377   const unsigned desiredHeight = desiredDimensions.GetHeight();
378
379   if( desiredWidth < 1U || desiredHeight < 1U )
380   {
381     DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
382   }
383   else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
384   {
385     const Vector2 desiredDims( desiredWidth, desiredHeight );
386
387     // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
388     // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
389     const float widthsRatio = inputWidth / float(desiredWidth);
390     const Vector2 scaledByWidth = desiredDims * widthsRatio;
391     const float heightsRatio = inputHeight / float(desiredHeight);
392     const Vector2 scaledByHeight = desiredDims * heightsRatio;
393     // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
394     const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
395     const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
396
397     // Work out how many pixels to trim from top and bottom, and left and right:
398     // (We only ever do one dimension)
399     const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - inputHeight) * 0.5f ) : 0;
400     const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - inputWidth) * 0.5f );
401
402     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, inputWidth, inputHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" );
403
404     // Make a new bitmap with the central part of the loaded one if required:
405     if( scanlinesToTrim > 0 || columnsToTrim > 0 )
406     {
407       const unsigned newWidth = inputWidth - 2 * columnsToTrim;
408       const unsigned newHeight = inputHeight - 2 * scanlinesToTrim;
409       BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
410       Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
411       DALI_ASSERT_DEBUG( packedView );
412       const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
413       packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
414
415       const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
416
417       const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
418       PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
419       DALI_ASSERT_DEBUG( srcPixels && destPixels );
420
421       // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
422       if( trimTopAndBottom )
423       {
424         memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
425       }
426       else
427       {
428         for( unsigned y = 0; y < newHeight; ++y )
429         {
430           memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
431         }
432       }
433
434       // Overwrite the loaded bitmap with the cropped version:
435       bitmap = croppedBitmap;
436     }
437   }
438
439   return bitmap;
440 }
441
442 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
443                                         ImageDimensions desired,
444                                         ImageAttributes::ScalingMode scalingMode,
445                                         ImageAttributes::FilterMode filterMode )
446 {
447   // Source dimensions as loaded from resources (e.g. filesystem):
448   const unsigned int bitmapWidth  = bitmap.GetImageWidth();
449   const unsigned int bitmapHeight = bitmap.GetImageHeight();
450   // Desired dimensions (the rectangle to fit the source image to):
451   const unsigned int desiredWidth = desired.GetWidth();
452   const unsigned int desiredHeight = desired.GetHeight();
453
454   BitmapPtr outputBitmap( &bitmap );
455
456   // If a different size than the raw one has been requested, resize the image:
457   if( bitmap.GetPackedPixelsProfile() &&
458       (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
459       ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
460   {
461     const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
462
463     // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
464     unsigned int shrunkWidth = -1, shrunkHeight = -1;
465     DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, scalingMode, filterMode, shrunkWidth, shrunkHeight );
466
467     // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
468     const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), scalingMode );
469     const unsigned int filteredWidth = filteredDimensions.GetWidth();
470     const unsigned int filteredHeight = filteredDimensions.GetHeight();
471
472     // Run a filter to scale down the bitmap if it needs it:
473     if( (filteredWidth < shrunkWidth || filteredHeight < shrunkHeight) &&
474         (filterMode == ImageAttributes::Nearest || filterMode == ImageAttributes::Linear ||
475          filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear) )
476     {
477       ///@note If a linear filter is requested, we do our best with a point filter for now.
478       PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, bitmap.GetBuffer(), filteredWidth, filteredHeight );
479
480       outputBitmap =  MakeBitmap( bitmap.GetBuffer(), pixelFormat, filteredWidth, filteredHeight );
481     }
482     // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
483     else if( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight )
484     {
485       outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
486     }
487   }
488
489   return outputBitmap;
490 }
491
492 namespace
493 {
494 /**
495  * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
496  * @param test Which combination of the two dimensions matter for terminating the filtering.
497  * @param scaledWidth The width of the current downscaled image.
498  * @param scaledHeight The height of the current downscaled image.
499  * @param desiredWidth The target width for the downscaling.
500  * @param desiredHeight The target height for the downscaling.
501  */
502 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
503 {
504   bool keepScaling = false;
505   const unsigned int nextWidth = scaledWidth >> 1u;
506   const unsigned int nextHeight = scaledHeight >> 1u;
507
508   if( nextWidth >= 1u && nextHeight >= 1u )
509   {
510     switch( test )
511     {
512       case BoxDimensionTestEither:
513       {
514         keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
515         break;
516       }
517       case BoxDimensionTestBoth:
518       {
519         keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
520         break;
521       }
522       case BoxDimensionTestX:
523       {
524         keepScaling = nextWidth >= desiredWidth;
525         break;
526       }
527       case BoxDimensionTestY:
528       {
529         keepScaling = nextHeight >= desiredHeight;
530         break;
531       }
532     }
533   }
534
535   return keepScaling;
536 }
537
538 /**
539  * @brief A shared implementation of the overall iterative box filter
540  * downscaling algorithm.
541  *
542  * Specialise this for particular pixel formats by supplying the number of bytes
543  * per pixel and two functions: one for averaging pairs of neighbouring pixels
544  * on a single scanline, and a second for averaging pixels at corresponding
545  * positions on different scanlines.
546  **/
547 template<
548   int BYTES_PER_PIXEL,
549   void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
550   void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
551 >
552 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
553                                   const unsigned int inputWidth,
554                                   const unsigned int inputHeight,
555                                   const unsigned int desiredWidth,
556                                   const unsigned int desiredHeight,
557                                   BoxDimensionTest dimensionTest,
558                                   unsigned& outWidth,
559                                   unsigned& outHeight )
560 {
561   if( pixels == 0 )
562   {
563     return;
564   }
565   ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
566
567   // Scale the image until it would be smaller than desired, stopping if the
568   // resulting height or width would be less than 1:
569   unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
570   while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
571   {
572     const unsigned int lastWidth = scaledWidth;
573     scaledWidth  >>= 1u;
574     scaledHeight >>= 1u;
575
576     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
577
578     const unsigned int lastScanlinePair = scaledHeight - 1;
579
580     // Scale pairs of scanlines until any spare one at the end is dropped:
581     for( unsigned int y = 0; y <= lastScanlinePair; ++y )
582     {
583       // Scale two scanlines horizontally:
584       HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
585       HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
586
587       // Scale vertical pairs of pixels while the last two scanlines are still warm in
588       // the CPU cache(s):
589       // Note, better access patterns for cache-coherence are possible for very large
590       // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
591       // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
592       AverageScanlines(
593           &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
594           &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
595           &pixels[y * scaledWidth * BYTES_PER_PIXEL],
596           scaledWidth );
597     }
598   }
599
600   ///@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.
601   outWidth = scaledWidth;
602   outHeight = scaledHeight;
603 }
604
605 }
606
607 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
608 {
609   DebugAssertScanlineParameters( pixels, width );
610
611   const unsigned int lastPair = EvenDown( width - 2 );
612
613   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
614   {
615     // Load all the byte pixel components we need:
616     const unsigned int c11 = pixels[pixel * 3];
617     const unsigned int c12 = pixels[pixel * 3 + 1];
618     const unsigned int c13 = pixels[pixel * 3 + 2];
619     const unsigned int c21 = pixels[pixel * 3 + 3];
620     const unsigned int c22 = pixels[pixel * 3 + 4];
621     const unsigned int c23 = pixels[pixel * 3 + 5];
622
623     // Save the averaged byte pixel components:
624     pixels[outPixel * 3]     = AverageComponent( c11, c21 );
625     pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
626     pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
627   }
628 }
629
630 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
631 {
632   DebugAssertScanlineParameters( pixels, width );
633   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
634
635   uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
636
637   const unsigned int lastPair = EvenDown( width - 2 );
638
639   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
640   {
641     const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
642     alignedPixels[outPixel] = averaged;
643   }
644 }
645
646 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
647 {
648   DebugAssertScanlineParameters( pixels, width );
649   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
650
651   uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
652
653   const unsigned int lastPair = EvenDown( width - 2 );
654
655   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
656   {
657     const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
658     alignedPixels[outPixel] = averaged;
659   }
660 }
661
662 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
663 {
664   DebugAssertScanlineParameters( pixels, width );
665
666   const unsigned int lastPair = EvenDown( width - 2 );
667
668   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
669   {
670     // Load all the byte pixel components we need:
671     const unsigned int c11 = pixels[pixel * 2];
672     const unsigned int c12 = pixels[pixel * 2 + 1];
673     const unsigned int c21 = pixels[pixel * 2 + 2];
674     const unsigned int c22 = pixels[pixel * 2 + 3];
675
676     // Save the averaged byte pixel components:
677     pixels[outPixel * 2]     = AverageComponent( c11, c21 );
678     pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
679   }
680 }
681
682 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
683 {
684   DebugAssertScanlineParameters( pixels, width );
685
686   const unsigned int lastPair = EvenDown( width - 2 );
687
688   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
689   {
690     // Load all the byte pixel components we need:
691     const unsigned int c1 = pixels[pixel];
692     const unsigned int c2 = pixels[pixel + 1];
693
694     // Save the averaged byte pixel component:
695     pixels[outPixel] = AverageComponent( c1, c2 );
696   }
697 }
698
699 /**
700  * @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.
701  * 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.
702  */
703 void AverageScanlines1( const unsigned char * const scanline1,
704                         const unsigned char * const __restrict__ scanline2,
705                         unsigned char* const outputScanline,
706                         const unsigned int width )
707 {
708   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
709
710   for( unsigned int component = 0; component < width; ++component )
711   {
712     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
713   }
714 }
715
716 void AverageScanlines2( const unsigned char * const scanline1,
717                         const unsigned char * const __restrict__ scanline2,
718                         unsigned char* const outputScanline,
719                         const unsigned int width )
720 {
721   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
722
723   for( unsigned int component = 0; component < width * 2; ++component )
724   {
725     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
726   }
727 }
728
729 void AverageScanlines3( const unsigned char * const scanline1,
730                         const unsigned char * const __restrict__ scanline2,
731                         unsigned char* const outputScanline,
732                         const unsigned int width )
733 {
734   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
735
736   for( unsigned int component = 0; component < width * 3; ++component )
737   {
738     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
739   }
740 }
741
742 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
743                                const unsigned char * const __restrict__ scanline2,
744                                unsigned char * const outputScanline,
745                                const unsigned int width )
746 {
747   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
748   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
749   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
750   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
751
752   const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
753   const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
754   uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
755
756   for( unsigned int pixel = 0; pixel < width; ++pixel )
757   {
758     alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
759   }
760 }
761
762 void AverageScanlinesRGB565( const unsigned char * const scanline1,
763                              const unsigned char * const __restrict__ scanline2,
764                              unsigned char * const outputScanline,
765                              const unsigned int width )
766 {
767   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
768   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
769   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
770   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
771
772   const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
773   const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
774   uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
775
776   for( unsigned int pixel = 0; pixel < width; ++pixel )
777   {
778     alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
779   }
780 }
781
782 /// Dispatch to pixel format appropriate box filter downscaling functions.
783 void DownscaleInPlacePow2( unsigned char * const pixels,
784                            Pixel::Format pixelFormat,
785                            unsigned int inputWidth,
786                            unsigned int inputHeight,
787                            unsigned int desiredWidth,
788                            unsigned int desiredHeight,
789                            ImageAttributes::ScalingMode scalingMode,
790                            ImageAttributes::FilterMode filterMode,
791                            unsigned& outWidth,
792                            unsigned& outHeight )
793 {
794   outWidth = inputWidth;
795   outHeight = inputHeight;
796   // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
797   if( filterMode == ImageAttributes::Box || filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear )
798   {
799     // Check the pixel format is one that is supported:
800     if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
801     {
802       const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( scalingMode );
803
804       if( pixelFormat == Pixel::RGBA8888 )
805       {
806         Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
807       }
808       else if( pixelFormat == Pixel::RGB888 )
809       {
810         Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
811       }
812       else if( pixelFormat == Pixel::RGB565 )
813       {
814         Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
815       }
816       else if( pixelFormat == Pixel::LA88 )
817       {
818         Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
819       }
820       else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
821       {
822         Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
823       }
824     }
825   }
826   else
827   {
828     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
829   }
830 }
831
832 void DownscaleInPlacePow2RGB888( unsigned char * const pixels,
833                                  const unsigned int inputWidth,
834                                  const unsigned int inputHeight,
835                                  const unsigned int desiredWidth,
836                                  const unsigned int desiredHeight,
837                                  BoxDimensionTest dimensionTest,
838                                  unsigned& outWidth,
839                                  unsigned& outHeight )
840 {
841   DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
842 }
843
844 void DownscaleInPlacePow2RGBA8888( unsigned char * const pixels,
845                                    const unsigned int inputWidth,
846                                    const unsigned int inputHeight,
847                                    const unsigned int desiredWidth,
848                                    const unsigned int desiredHeight,
849                                    BoxDimensionTest dimensionTest,
850                                    unsigned& outWidth,
851                                    unsigned& outHeight )
852 {
853   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
854   DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
855 }
856
857 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
858                                  unsigned int inputWidth,
859                                  unsigned int inputHeight,
860                                  unsigned int desiredWidth,
861                                  unsigned int desiredHeight,
862                                  BoxDimensionTest dimensionTest,
863                                  unsigned int& outWidth,
864                                  unsigned int& outHeight )
865 {
866   DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
867 }
868
869 /**
870  * @copydoc DownscaleInPlacePow2RGB888
871  *
872  * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
873  */
874 void DownscaleInPlacePow2ComponentPair( unsigned char * const pixels,
875                                         const unsigned int inputWidth,
876                                         const unsigned int inputHeight,
877                                         const unsigned int desiredWidth,
878                                         const unsigned int desiredHeight,
879                                         BoxDimensionTest dimensionTest,
880                                         unsigned& outWidth,
881                                         unsigned& outHeight )
882 {
883   DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
884 }
885
886 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
887                                              unsigned int inputWidth,
888                                              unsigned int inputHeight,
889                                              unsigned int desiredWidth,
890                                              unsigned int desiredHeight,
891                                              BoxDimensionTest dimensionTest,
892                                              unsigned int& outWidth,
893                                              unsigned int& outHeight )
894 {
895   DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
896 }
897
898 namespace
899 {
900
901 // Point sample an image to a new resolution (like GL_NEAREST).
902 template<typename PIXEL>
903 void PointSampleAddressablePixels( const uint8_t * inPixels,
904                                    unsigned int inputWidth,
905                                    unsigned int inputHeight,
906                                    uint8_t * outPixels,
907                                    unsigned int desiredWidth,
908                                    unsigned int desiredHeight )
909 {
910   DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
911       outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
912       "The input and output buffers must not overlap for an upscaling.");
913   DALI_ASSERT_DEBUG( ((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, ...)." );
914   DALI_ASSERT_DEBUG( ((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, ...)." );
915
916   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
917   {
918     return;
919   }
920   const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
921   PIXEL* const       outAligned = reinterpret_cast<PIXEL*>(outPixels);
922   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
923   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
924
925   unsigned int inY = 0;
926   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
927   {
928     // Round fixed point y coordinate to nearest integer:
929     const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
930     const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
931     PIXEL* const outScanline = &outAligned[desiredWidth * outY];
932
933     DALI_ASSERT_DEBUG( integerY < inputHeight );
934     DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
935     DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
936
937     unsigned int inX = 0;
938     for( unsigned int outX = 0; outX < desiredWidth; ++outX )
939     {
940       // Round the fixed-point x coordinate to an integer:
941       const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
942       const PIXEL* const inPixelAddress = &inScanline[integerX];
943       const PIXEL pixel = *inPixelAddress;
944       outScanline[outX] = pixel;
945       inX += deltaX;
946     }
947     inY += deltaY;
948   }
949 }
950
951 }
952
953 /*
954  * RGBA8888
955  */
956 void PointSample4BPP( const unsigned char * inPixels,
957                       unsigned int inputWidth,
958                       unsigned int inputHeight,
959                       unsigned char * outPixels,
960                       unsigned int desiredWidth,
961                       unsigned int desiredHeight )
962 {
963   PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
964 }
965
966 /*
967  * RGB565, LA88
968  */
969 void PointSample2BPP( const unsigned char * inPixels,
970                       unsigned int inputWidth,
971                       unsigned int inputHeight,
972                       unsigned char * outPixels,
973                       unsigned int desiredWidth,
974                       unsigned int desiredHeight )
975 {
976   PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
977 }
978
979 /*
980  * L8, A8
981  */
982 void PointSample1BPP( const unsigned char * inPixels,
983                       unsigned int inputWidth,
984                       unsigned int inputHeight,
985                       unsigned char * outPixels,
986                       unsigned int desiredWidth,
987                       unsigned int desiredHeight )
988 {
989   PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
990 }
991
992 /*
993  * RGB888
994  * RGB888 is a special case as its pixels are not aligned addressable units.
995  */
996 void PointSample3BPP( const uint8_t * inPixels,
997                       unsigned int inputWidth,
998                       unsigned int inputHeight,
999                       uint8_t * outPixels,
1000                       unsigned int desiredWidth,
1001                       unsigned int desiredHeight )
1002 {
1003   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1004   {
1005     return;
1006   }
1007   const unsigned int BYTES_PER_PIXEL = 3;
1008
1009   // Generate fixed-point 16.16 deltas in input image coordinates:
1010   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
1011   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1012
1013   // Step through output image in whole integer pixel steps while tracking the
1014   // corresponding locations in the input image using 16.16 fixed-point
1015   // coordinates:
1016   unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1017   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1018   {
1019     const uint8_t* const inScanline = &inPixels[inputWidth * (inY >> 16u) * BYTES_PER_PIXEL];
1020     uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1021     unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1022
1023     for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1024     {
1025       // Truncate the fixed-point input coordinate to the address of the input pixel to sample:
1026       const uint8_t* const inPixelAddress = &inScanline[(inX >> 16u) * BYTES_PER_PIXEL];
1027
1028       // Issue loads for all pixel color components up-front:
1029       const unsigned int c0 = inPixelAddress[0];
1030       const unsigned int c1 = inPixelAddress[1];
1031       const unsigned int c2 = inPixelAddress[2];
1032       ///@ToDo: Benchmark one 32bit load that will be unaligned 2/3 of the time + 3 rotate and masks, versus these three aligned byte loads.
1033
1034       // Output the pixel components:
1035       outScanline[outX]     = c0;
1036       outScanline[outX + 1] = c1;
1037       outScanline[outX + 2] = c2;
1038
1039       // Increment the fixed-point input coordinate:
1040       inX += deltaX;
1041     }
1042
1043     inY += deltaY;
1044   }
1045 }
1046
1047 // Dispatch to a format-appropriate point sampling function:
1048 void PointSample( const unsigned char * inPixels,
1049                   unsigned int inputWidth,
1050                   unsigned int inputHeight,
1051                   Pixel::Format pixelFormat,
1052                   unsigned char * outPixels,
1053                   unsigned int desiredWidth,
1054                   unsigned int desiredHeight )
1055 {
1056   // Check the pixel format is one that is supported:
1057   if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1058   {
1059     if( pixelFormat == Pixel::RGBA8888 )
1060     {
1061       PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1062     }
1063     else if( pixelFormat == Pixel::RGB888 )
1064     {
1065       PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1066     }
1067     else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1068     {
1069       PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1070     }
1071     else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
1072     {
1073       PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1074     }
1075   }
1076   else
1077   {
1078     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1079   }
1080 }
1081
1082 } /* namespace Platform */
1083 } /* namespace Internal */
1084 } /* namespace Dali */