Merge branch 'tizen' into devel/new_mesh
[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
37 using Integration::Bitmap;
38 using Integration::BitmapPtr;
39 typedef unsigned char PixelBuffer;
40
41 /**
42  * @brief 4 byte pixel structure.
43  */
44 struct Pixel4Bytes
45 {
46   uint8_t r;
47   uint8_t g;
48   uint8_t b;
49   uint8_t a;
50 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
51
52 /**
53  * @brief RGB888 pixel structure.
54  */
55 struct Pixel3Bytes
56 {
57   uint8_t r;
58   uint8_t g;
59   uint8_t b;
60 } __attribute__((packed, aligned(1)));
61
62 /**
63  * @brief RGB565 pixel typedefed from a short.
64  *
65  * Access fields by manual shifting and masking.
66  */
67 typedef uint16_t PixelRGB565;
68
69 /**
70  * @brief a Pixel composed of two independent byte components.
71  */
72 struct Pixel2Bytes
73 {
74   uint8_t l;
75   uint8_t a;
76 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
77
78
79 #if defined(DEBUG_ENABLED)
80 /**
81  * Disable logging of image operations or make it verbose from the commandline
82  * as follows (e.g., for dali demo app):
83  * <code>
84  * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
85  * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
86  * </code>
87  */
88 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
89 #endif
90
91 /** @return The greatest even number less than or equal to the argument. */
92 inline unsigned int EvenDown( const unsigned int a )
93 {
94   const unsigned int evened = a & ~1u;
95   return evened;
96 }
97
98 /**
99  * @brief Log bad parameters.
100  */
101 void ValidateScalingParameters( const unsigned int inputWidth,
102                                 const unsigned int inputHeight,
103                                 const unsigned int desiredWidth,
104                                 const unsigned int desiredHeight )
105 {
106   if( desiredWidth > inputWidth || desiredHeight > inputHeight )
107   {
108     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
109   }
110
111   if( desiredWidth == 0u || desiredHeight == 0u )
112   {
113     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
114   }
115
116   if( inputWidth == 0u || inputHeight == 0u )
117   {
118     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
119   }
120 }
121
122 /**
123  * @brief Do debug assertions common to all scanline halving functions.
124  * @note Inline and in anon namespace so should boil away in release builds.
125  */
126 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
127 {
128   DALI_ASSERT_DEBUG( pixels && "Null pointer." );
129   DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
130   DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
131 }
132
133 /**
134  * @brief Assertions on params to functions averaging pairs of scanlines.
135  * @note Inline as intended to boil away in release.
136  */
137 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
138                                                const uint8_t * const scanline2,
139                                                uint8_t* const outputScanline,
140                                                const size_t widthInComponents )
141 {
142   DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
143   DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
144   DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
145   DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
146   DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
147 }
148
149 /**
150  * @brief Work out the desired width and height according to the rules documented for the ImageAttributes class.
151  *
152  * @param[in] bitmapWidth Width of image before processing.
153  * @param[in] bitmapHeight Height of image before processing.
154  * @param[in] requestedWidth Width of area to scale image into. Can be zero.
155  * @param[in] requestedHeight Height of area to scale image into. Can be zero.
156  * @return Dimensions of area to scale image into after special rules are applied.
157  * @see ImageAttributes
158  */
159 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
160 {
161   // If no dimensions have been requested, default to the source ones:
162   if( requestedWidth == 0 && requestedHeight == 0 )
163   {
164     return ImageDimensions( bitmapWidth, bitmapHeight );
165   }
166
167   // If both dimensions have values requested, use them both:
168   if( requestedWidth != 0 && requestedHeight != 0 )
169   {
170     return ImageDimensions( requestedWidth, requestedHeight );
171   }
172
173   // If only one of the dimensions has been requested, calculate the other from
174   // the requested one and the source image aspect ratio:
175
176   if( requestedWidth != 0 )
177   {
178     return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
179   }
180   return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
181 }
182
183 /**
184  * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
185  */
186 BoxDimensionTest DimensionTestForScalingMode( ImageAttributes::ScalingMode scalingMode )
187 {
188   BoxDimensionTest dimensionTest;
189   dimensionTest = BoxDimensionTestEither;
190
191   switch( scalingMode )
192   {
193     // Shrink to fit attempts to make one or zero dimensions smaller than the
194     // desired dimensions and one or two dimensions exactly the same as the desired
195     // ones, so as long as one dimension is larger than the desired size, box
196     // filtering can continue even if the second dimension is smaller than the
197     // desired dimensions:
198     case ImageAttributes::ShrinkToFit:
199     {
200       dimensionTest = BoxDimensionTestEither;
201       break;
202     }
203     // Scale to fill mode keeps both dimensions at least as large as desired:
204     case ImageAttributes::ScaleToFill:
205     {
206       dimensionTest = BoxDimensionTestBoth;
207       break;
208     }
209     // Y dimension is irrelevant when downscaling in FitWidth mode:
210     case ImageAttributes::FitWidth:
211     {
212       dimensionTest = BoxDimensionTestX;
213       break;
214     }
215     // X Dimension is ignored by definition in FitHeight mode:
216     case ImageAttributes::FitHeight:
217     {
218       dimensionTest = BoxDimensionTestY;
219       break;
220     }
221   }
222
223   return dimensionTest;
224 }
225
226 /**
227  * @brief Work out the dimensions for a uniform scaling of the input to map it
228  * into the target while effecting ShinkToFit scaling mode.
229  */
230 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
231 {
232   DALI_ASSERT_DEBUG( true && " " );
233   // Scale the input by the least extreme of the two dimensions:
234   const float widthScale  = target.GetX() / float(source.GetX());
235   const float heightScale = target.GetY() / float(source.GetY());
236   const float scale = widthScale < heightScale ? widthScale : heightScale;
237
238   // Do no scaling at all if the result would increase area:
239   if( scale >= 1.0f )
240   {
241     return source;
242   }
243
244   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
245 }
246
247 /**
248  * @brief Work out the dimensions for a uniform scaling of the input to map it
249  * into the target while effecting ScaleToFill scaling mode.
250  * @note The output dimensions will need either top and bottom or left and right
251  * to be cropped away unless the source was pre-cropped to match the destination
252  * aspect ratio.
253  */
254 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
255 {
256   DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0  && "Zero-area rectangles should not be passed-in" );
257   // Scale the input by the least extreme of the two dimensions:
258   const float widthScale  = target.GetX() / float(source.GetX());
259   const float heightScale = target.GetY() / float(source.GetY());
260   const float scale = widthScale > heightScale ? widthScale : heightScale;
261
262   // Do no scaling at all if the result would increase area:
263   if( scale >= 1.0f )
264   {
265     return source;
266   }
267
268   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
269 }
270
271 /**
272  * @brief Work out the dimensions for a uniform scaling of the input to map it
273  * into the target while effecting FitWidth scaling mode.
274  */
275 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
276 {
277   DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
278   const float scale  = target.GetX() / float(source.GetX());
279
280   // Do no scaling at all if the result would increase area:
281   if( scale >= 1.0f )
282   {
283    return source;
284   }
285   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
286 }
287
288 /**
289  * @brief Work out the dimensions for a uniform scaling of the input to map it
290  * into the target while effecting FitHeight scaling mode.
291  */
292 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
293 {
294   DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
295   const float scale = target.GetY() / float(source.GetY());
296
297   // Do no scaling at all if the result would increase area:
298   if( scale >= 1.0f )
299   {
300     return source;
301   }
302
303   return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
304 }
305
306 /**
307  * @brief Generate the rectangle to use as the target of a pixel sampling pass
308  * (e.g., nearest or linear).
309  */
310 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, ImageAttributes::ScalingMode scalingMode )
311 {
312   ImageDimensions fitDimensions;
313   switch( scalingMode )
314   {
315     case ImageAttributes::ShrinkToFit:
316     {
317       fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
318       break;
319     }
320     case ImageAttributes::ScaleToFill:
321     {
322       fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
323       break;
324     }
325     case ImageAttributes::FitWidth:
326     {
327       fitDimensions = FitForFitWidth( requestedSize, sourceSize );
328       break;
329     }
330     case ImageAttributes::FitHeight:
331     {
332       fitDimensions = FitForFitHeight( requestedSize, sourceSize );
333       break;
334     }
335   };
336
337   return fitDimensions;
338 }
339
340 /**
341  * @brief Construct a bitmap with format and dimensions requested.
342  */
343 BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
344 {
345   DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
346
347   // Allocate a pixel buffer to hold the image passed in:
348   Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
349   newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
350   return newBitmap;
351 }
352
353 /**
354  * @brief Construct a bitmap object from a copy of the pixel array passed in.
355  */
356 BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
357 {
358   DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
359
360   // Allocate a pixel buffer to hold the image passed in:
361   Integration::BitmapPtr newBitmap = MakeEmptyBitmap( pixelFormat, width, height );
362
363   // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
364   memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
365   return newBitmap;
366 }
367
368 } // namespace - unnamed
369
370 /**
371  * @brief Implement ImageAttributes::ScaleTofill scaling mode cropping.
372  *
373  * Implement the cropping required for ImageAttributes::ScaleToFill mode,
374  * returning a new bitmap with the aspect ratio specified by the scaling mode.
375  * This scaling mode selects the central portion of a source image so any spare
376  * pixels off one of either the top or bottom edge need to be removed.
377  *
378  * @note If the input bitmap was not previously downscaled to exactly encompass
379  * the desired output size, the resulting bitmap will have the correct aspect
380  * ratio but will have larger dimensions than requested. This can be used to
381  * fake the scaling mode by relying on the GPU scaling at render time.
382  * If the input bitmap was previously maximally downscaled using a
383  * repeated box filter, this is a reasonable approach.
384  *
385  * @return The bitmap passed in if no scaling is needed or possible, else a new,
386  * smaller bitmap with the cropping required for the scaling mode applied.
387  */
388 Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
389
390 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
391 {
392   ///@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.
393
394   if( bitmap )
395   {
396     // Calculate the desired box, accounting for a possible zero component:
397     const ImageDimensions desiredDimensions  = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), requestedAttributes.GetWidth(), requestedAttributes.GetHeight() );
398
399     // If a different size than the raw one has been requested, resize the image
400     // maximally using a repeated box filter without making it smaller than the
401     // requested size in either dimension:
402     bitmap = DownscaleBitmap( *bitmap, desiredDimensions, requestedAttributes.GetScalingMode(), requestedAttributes.GetFilterMode() );
403
404     // Cut the bitmap according to the desired width and height so that the
405     // resulting bitmap has the same aspect ratio as the desired dimensions:
406     if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
407     {
408       bitmap = CropForScaleToFill( bitmap, desiredDimensions );
409     }
410
411     // Examine the image pixels remaining after cropping and scaling to see if all
412     // are opaque, allowing faster rendering, or some have non-1.0 alpha:
413     if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
414     {
415       bitmap->GetPackedPixelsProfile()->TestForTransparency();
416     }
417   }
418
419   return bitmap;
420 }
421
422 BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
423 {
424   const unsigned inputWidth = bitmap->GetImageWidth();
425   const unsigned inputHeight = bitmap->GetImageHeight();
426   const unsigned desiredWidth = desiredDimensions.GetWidth();
427   const unsigned desiredHeight = desiredDimensions.GetHeight();
428
429   if( desiredWidth < 1U || desiredHeight < 1U )
430   {
431     DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
432   }
433   else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
434   {
435     const Vector2 desiredDims( desiredWidth, desiredHeight );
436
437     // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
438     // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
439     const float widthsRatio = inputWidth / float(desiredWidth);
440     const Vector2 scaledByWidth = desiredDims * widthsRatio;
441     const float heightsRatio = inputHeight / float(desiredHeight);
442     const Vector2 scaledByHeight = desiredDims * heightsRatio;
443     // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
444     const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
445     const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
446
447     // Work out how many pixels to trim from top and bottom, and left and right:
448     // (We only ever do one dimension)
449     const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - inputHeight) * 0.5f ) : 0;
450     const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - inputWidth) * 0.5f );
451
452     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" );
453
454     // Make a new bitmap with the central part of the loaded one if required:
455     if( scanlinesToTrim > 0 || columnsToTrim > 0 )
456     {
457       const unsigned newWidth = inputWidth - 2 * columnsToTrim;
458       const unsigned newHeight = inputHeight - 2 * scanlinesToTrim;
459       BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
460       Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
461       DALI_ASSERT_DEBUG( packedView );
462       const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
463       packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
464
465       const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
466
467       const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
468       PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
469       DALI_ASSERT_DEBUG( srcPixels && destPixels );
470
471       // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
472       if( trimTopAndBottom )
473       {
474         memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
475       }
476       else
477       {
478         for( unsigned y = 0; y < newHeight; ++y )
479         {
480           memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
481         }
482       }
483
484       // Overwrite the loaded bitmap with the cropped version:
485       bitmap = croppedBitmap;
486     }
487   }
488
489   return bitmap;
490 }
491
492 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
493                                         ImageDimensions desired,
494                                         ImageAttributes::ScalingMode scalingMode,
495                                         ImageAttributes::FilterMode filterMode )
496 {
497   // Source dimensions as loaded from resources (e.g. filesystem):
498   const unsigned int bitmapWidth  = bitmap.GetImageWidth();
499   const unsigned int bitmapHeight = bitmap.GetImageHeight();
500   // Desired dimensions (the rectangle to fit the source image to):
501   const unsigned int desiredWidth = desired.GetWidth();
502   const unsigned int desiredHeight = desired.GetHeight();
503
504   BitmapPtr outputBitmap( &bitmap );
505
506   // If a different size than the raw one has been requested, resize the image:
507   if( bitmap.GetPackedPixelsProfile() &&
508       (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
509       ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
510   {
511     const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
512
513     // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
514     unsigned int shrunkWidth = -1, shrunkHeight = -1;
515     DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, scalingMode, filterMode, shrunkWidth, shrunkHeight );
516
517     // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
518     const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), scalingMode );
519     const unsigned int filteredWidth = filteredDimensions.GetWidth();
520     const unsigned int filteredHeight = filteredDimensions.GetHeight();
521
522     // Run a filter to scale down the bitmap if it needs it:
523     bool filtered = false;
524     if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
525     {
526       if( filterMode == ImageAttributes::Linear || filterMode == ImageAttributes::BoxThenLinear ||
527           filterMode == ImageAttributes::Nearest || filterMode == ImageAttributes::BoxThenNearest )
528       {
529         outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
530         if( outputBitmap )
531         {
532           if( filterMode == ImageAttributes::Linear || filterMode == ImageAttributes::BoxThenLinear )
533           {
534             LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
535           }
536           else
537           {
538             PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight );
539           }
540           filtered = true;
541         }
542       }
543     }
544     // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
545     if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
546     {
547       outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
548     }
549   }
550
551   return outputBitmap;
552 }
553
554 namespace
555 {
556 /**
557  * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
558  * @param test Which combination of the two dimensions matter for terminating the filtering.
559  * @param scaledWidth The width of the current downscaled image.
560  * @param scaledHeight The height of the current downscaled image.
561  * @param desiredWidth The target width for the downscaling.
562  * @param desiredHeight The target height for the downscaling.
563  */
564 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
565 {
566   bool keepScaling = false;
567   const unsigned int nextWidth = scaledWidth >> 1u;
568   const unsigned int nextHeight = scaledHeight >> 1u;
569
570   if( nextWidth >= 1u && nextHeight >= 1u )
571   {
572     switch( test )
573     {
574       case BoxDimensionTestEither:
575       {
576         keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
577         break;
578       }
579       case BoxDimensionTestBoth:
580       {
581         keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
582         break;
583       }
584       case BoxDimensionTestX:
585       {
586         keepScaling = nextWidth >= desiredWidth;
587         break;
588       }
589       case BoxDimensionTestY:
590       {
591         keepScaling = nextHeight >= desiredHeight;
592         break;
593       }
594     }
595   }
596
597   return keepScaling;
598 }
599
600 /**
601  * @brief A shared implementation of the overall iterative box filter
602  * downscaling algorithm.
603  *
604  * Specialise this for particular pixel formats by supplying the number of bytes
605  * per pixel and two functions: one for averaging pairs of neighbouring pixels
606  * on a single scanline, and a second for averaging pixels at corresponding
607  * positions on different scanlines.
608  **/
609 template<
610   int BYTES_PER_PIXEL,
611   void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
612   void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
613 >
614 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
615                                   const unsigned int inputWidth,
616                                   const unsigned int inputHeight,
617                                   const unsigned int desiredWidth,
618                                   const unsigned int desiredHeight,
619                                   BoxDimensionTest dimensionTest,
620                                   unsigned& outWidth,
621                                   unsigned& outHeight )
622 {
623   if( pixels == 0 )
624   {
625     return;
626   }
627   ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
628
629   // Scale the image until it would be smaller than desired, stopping if the
630   // resulting height or width would be less than 1:
631   unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
632   while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
633   {
634     const unsigned int lastWidth = scaledWidth;
635     scaledWidth  >>= 1u;
636     scaledHeight >>= 1u;
637
638     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
639
640     const unsigned int lastScanlinePair = scaledHeight - 1;
641
642     // Scale pairs of scanlines until any spare one at the end is dropped:
643     for( unsigned int y = 0; y <= lastScanlinePair; ++y )
644     {
645       // Scale two scanlines horizontally:
646       HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
647       HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
648
649       // Scale vertical pairs of pixels while the last two scanlines are still warm in
650       // the CPU cache(s):
651       // Note, better access patterns for cache-coherence are possible for very large
652       // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
653       // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
654       AverageScanlines(
655           &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
656           &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
657           &pixels[y * scaledWidth * BYTES_PER_PIXEL],
658           scaledWidth );
659     }
660   }
661
662   ///@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.
663   outWidth = scaledWidth;
664   outHeight = scaledHeight;
665 }
666
667 }
668
669 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
670 {
671   DebugAssertScanlineParameters( pixels, width );
672
673   const unsigned int lastPair = EvenDown( width - 2 );
674
675   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
676   {
677     // Load all the byte pixel components we need:
678     const unsigned int c11 = pixels[pixel * 3];
679     const unsigned int c12 = pixels[pixel * 3 + 1];
680     const unsigned int c13 = pixels[pixel * 3 + 2];
681     const unsigned int c21 = pixels[pixel * 3 + 3];
682     const unsigned int c22 = pixels[pixel * 3 + 4];
683     const unsigned int c23 = pixels[pixel * 3 + 5];
684
685     // Save the averaged byte pixel components:
686     pixels[outPixel * 3]     = AverageComponent( c11, c21 );
687     pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
688     pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
689   }
690 }
691
692 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
693 {
694   DebugAssertScanlineParameters( pixels, width );
695   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
696
697   uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
698
699   const unsigned int lastPair = EvenDown( width - 2 );
700
701   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
702   {
703     const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
704     alignedPixels[outPixel] = averaged;
705   }
706 }
707
708 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
709 {
710   DebugAssertScanlineParameters( pixels, width );
711   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
712
713   uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
714
715   const unsigned int lastPair = EvenDown( width - 2 );
716
717   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
718   {
719     const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
720     alignedPixels[outPixel] = averaged;
721   }
722 }
723
724 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
725 {
726   DebugAssertScanlineParameters( pixels, width );
727
728   const unsigned int lastPair = EvenDown( width - 2 );
729
730   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
731   {
732     // Load all the byte pixel components we need:
733     const unsigned int c11 = pixels[pixel * 2];
734     const unsigned int c12 = pixels[pixel * 2 + 1];
735     const unsigned int c21 = pixels[pixel * 2 + 2];
736     const unsigned int c22 = pixels[pixel * 2 + 3];
737
738     // Save the averaged byte pixel components:
739     pixels[outPixel * 2]     = AverageComponent( c11, c21 );
740     pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
741   }
742 }
743
744 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
745 {
746   DebugAssertScanlineParameters( pixels, width );
747
748   const unsigned int lastPair = EvenDown( width - 2 );
749
750   for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
751   {
752     // Load all the byte pixel components we need:
753     const unsigned int c1 = pixels[pixel];
754     const unsigned int c2 = pixels[pixel + 1];
755
756     // Save the averaged byte pixel component:
757     pixels[outPixel] = AverageComponent( c1, c2 );
758   }
759 }
760
761 /**
762  * @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.
763  * 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.
764  */
765 void AverageScanlines1( const unsigned char * const scanline1,
766                         const unsigned char * const __restrict__ scanline2,
767                         unsigned char* const outputScanline,
768                         const unsigned int width )
769 {
770   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
771
772   for( unsigned int component = 0; component < width; ++component )
773   {
774     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
775   }
776 }
777
778 void AverageScanlines2( const unsigned char * const scanline1,
779                         const unsigned char * const __restrict__ scanline2,
780                         unsigned char* const outputScanline,
781                         const unsigned int width )
782 {
783   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
784
785   for( unsigned int component = 0; component < width * 2; ++component )
786   {
787     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
788   }
789 }
790
791 void AverageScanlines3( const unsigned char * const scanline1,
792                         const unsigned char * const __restrict__ scanline2,
793                         unsigned char* const outputScanline,
794                         const unsigned int width )
795 {
796   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
797
798   for( unsigned int component = 0; component < width * 3; ++component )
799   {
800     outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
801   }
802 }
803
804 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
805                                const unsigned char * const __restrict__ scanline2,
806                                unsigned char * const outputScanline,
807                                const unsigned int width )
808 {
809   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
810   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
811   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
812   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
813
814   const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
815   const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
816   uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
817
818   for( unsigned int pixel = 0; pixel < width; ++pixel )
819   {
820     alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
821   }
822 }
823
824 void AverageScanlinesRGB565( const unsigned char * const scanline1,
825                              const unsigned char * const __restrict__ scanline2,
826                              unsigned char * const outputScanline,
827                              const unsigned int width )
828 {
829   DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
830   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
831   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
832   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
833
834   const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
835   const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
836   uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
837
838   for( unsigned int pixel = 0; pixel < width; ++pixel )
839   {
840     alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
841   }
842 }
843
844 /// Dispatch to pixel format appropriate box filter downscaling functions.
845 void DownscaleInPlacePow2( unsigned char * const pixels,
846                            Pixel::Format pixelFormat,
847                            unsigned int inputWidth,
848                            unsigned int inputHeight,
849                            unsigned int desiredWidth,
850                            unsigned int desiredHeight,
851                            ImageAttributes::ScalingMode scalingMode,
852                            ImageAttributes::FilterMode filterMode,
853                            unsigned& outWidth,
854                            unsigned& outHeight )
855 {
856   outWidth = inputWidth;
857   outHeight = inputHeight;
858   // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
859   if( filterMode == ImageAttributes::Box || filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear )
860   {
861     // Check the pixel format is one that is supported:
862     if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
863     {
864       const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( scalingMode );
865
866       if( pixelFormat == Pixel::RGBA8888 )
867       {
868         Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
869       }
870       else if( pixelFormat == Pixel::RGB888 )
871       {
872         Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
873       }
874       else if( pixelFormat == Pixel::RGB565 )
875       {
876         Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
877       }
878       else if( pixelFormat == Pixel::LA88 )
879       {
880         Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
881       }
882       else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
883       {
884         Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
885       }
886       else
887       {
888         DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
889       }
890     }
891   }
892   else
893   {
894     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
895   }
896 }
897
898 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
899                                  unsigned int inputWidth,
900                                  unsigned int inputHeight,
901                                  unsigned int desiredWidth,
902                                  unsigned int desiredHeight,
903                                  BoxDimensionTest dimensionTest,
904                                  unsigned& outWidth,
905                                  unsigned& outHeight )
906 {
907   DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
908 }
909
910 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
911                                    unsigned int inputWidth,
912                                    unsigned int inputHeight,
913                                    unsigned int desiredWidth,
914                                    unsigned int desiredHeight,
915                                    BoxDimensionTest dimensionTest,
916                                    unsigned& outWidth,
917                                    unsigned& outHeight )
918 {
919   DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
920   DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
921 }
922
923 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
924                                  unsigned int inputWidth,
925                                  unsigned int inputHeight,
926                                  unsigned int desiredWidth,
927                                  unsigned int desiredHeight,
928                                  BoxDimensionTest dimensionTest,
929                                  unsigned int& outWidth,
930                                  unsigned int& outHeight )
931 {
932   DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
933 }
934
935 /**
936  * @copydoc DownscaleInPlacePow2RGB888
937  *
938  * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
939  */
940 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
941                                         unsigned int inputWidth,
942                                         unsigned int inputHeight,
943                                         unsigned int desiredWidth,
944                                         unsigned int desiredHeight,
945                                         BoxDimensionTest dimensionTest,
946                                         unsigned& outWidth,
947                                         unsigned& outHeight )
948 {
949   DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
950 }
951
952 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
953                                              unsigned int inputWidth,
954                                              unsigned int inputHeight,
955                                              unsigned int desiredWidth,
956                                              unsigned int desiredHeight,
957                                              BoxDimensionTest dimensionTest,
958                                              unsigned int& outWidth,
959                                              unsigned int& outHeight )
960 {
961   DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
962 }
963
964 namespace
965 {
966
967 /**
968  * @brief Point sample an image to a new resolution (like GL_NEAREST).
969  *
970  * Template is used purely as a type-safe code generator in this one
971  * compilation unit. Generated code is inlined into type-specific wrapper
972  * functions below which are exported to rest of module.
973  */
974 template<typename PIXEL>
975 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
976                                    unsigned int inputWidth,
977                                    unsigned int inputHeight,
978                                    uint8_t * outPixels,
979                                    unsigned int desiredWidth,
980                                    unsigned int desiredHeight )
981 {
982   DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
983       outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
984       "The input and output buffers must not overlap for an upscaling.");
985   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, ...)." );
986   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, ...)." );
987
988   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
989   {
990     return;
991   }
992   const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
993   PIXEL* const       outAligned = reinterpret_cast<PIXEL*>(outPixels);
994   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
995   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
996
997   unsigned int inY = 0;
998   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
999   {
1000     // Round fixed point y coordinate to nearest integer:
1001     const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1002     const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1003     PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1004
1005     DALI_ASSERT_DEBUG( integerY < inputHeight );
1006     DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1007     DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1008
1009     unsigned int inX = 0;
1010     for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1011     {
1012       // Round the fixed-point x coordinate to an integer:
1013       const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1014       const PIXEL* const inPixelAddress = &inScanline[integerX];
1015       const PIXEL pixel = *inPixelAddress;
1016       outScanline[outX] = pixel;
1017       inX += deltaX;
1018     }
1019     inY += deltaY;
1020   }
1021 }
1022
1023 }
1024
1025 // RGBA8888
1026 void PointSample4BPP( const unsigned char * inPixels,
1027                       unsigned int inputWidth,
1028                       unsigned int inputHeight,
1029                       unsigned char * outPixels,
1030                       unsigned int desiredWidth,
1031                       unsigned int desiredHeight )
1032 {
1033   PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1034 }
1035
1036 // RGB565, LA88
1037 void PointSample2BPP( const unsigned char * inPixels,
1038                       unsigned int inputWidth,
1039                       unsigned int inputHeight,
1040                       unsigned char * outPixels,
1041                       unsigned int desiredWidth,
1042                       unsigned int desiredHeight )
1043 {
1044   PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1045 }
1046
1047 // L8, A8
1048 void PointSample1BPP( const unsigned char * inPixels,
1049                       unsigned int inputWidth,
1050                       unsigned int inputHeight,
1051                       unsigned char * outPixels,
1052                       unsigned int desiredWidth,
1053                       unsigned int desiredHeight )
1054 {
1055   PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1056 }
1057
1058 /* RGB888
1059  * RGB888 is a special case as its pixels are not aligned addressable units.
1060  */
1061 void PointSample3BPP( const uint8_t * inPixels,
1062                       unsigned int inputWidth,
1063                       unsigned int inputHeight,
1064                       uint8_t * outPixels,
1065                       unsigned int desiredWidth,
1066                       unsigned int desiredHeight )
1067 {
1068   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1069   {
1070     return;
1071   }
1072   const unsigned int BYTES_PER_PIXEL = 3;
1073
1074   // Generate fixed-point 16.16 deltas in input image coordinates:
1075   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
1076   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1077
1078   // Step through output image in whole integer pixel steps while tracking the
1079   // corresponding locations in the input image using 16.16 fixed-point
1080   // coordinates:
1081   unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1082   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1083   {
1084     const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1085     const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1086     uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1087     unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1088
1089     for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1090     {
1091       // Round the fixed-point input coordinate to the address of the input pixel to sample:
1092       const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1093       const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1094
1095       // Issue loads for all pixel color components up-front:
1096       const unsigned int c0 = inPixelAddress[0];
1097       const unsigned int c1 = inPixelAddress[1];
1098       const unsigned int c2 = inPixelAddress[2];
1099       ///@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.
1100
1101       // Output the pixel components:
1102       outScanline[outX]     = c0;
1103       outScanline[outX + 1] = c1;
1104       outScanline[outX + 2] = c2;
1105
1106       // Increment the fixed-point input coordinate:
1107       inX += deltaX;
1108     }
1109
1110     inY += deltaY;
1111   }
1112 }
1113
1114 // Dispatch to a format-appropriate point sampling function:
1115 void PointSample( const unsigned char * inPixels,
1116                   unsigned int inputWidth,
1117                   unsigned int inputHeight,
1118                   Pixel::Format pixelFormat,
1119                   unsigned char * outPixels,
1120                   unsigned int desiredWidth,
1121                   unsigned int desiredHeight )
1122 {
1123   // Check the pixel format is one that is supported:
1124   if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1125   {
1126     if( pixelFormat == Pixel::RGB888 )
1127     {
1128       PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1129     }
1130     else if( pixelFormat == Pixel::RGBA8888 )
1131     {
1132       PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1133     }
1134     else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1135     {
1136       PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1137     }
1138     else if( pixelFormat == Pixel::L8  || pixelFormat == Pixel::A8 )
1139     {
1140       PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1141     }
1142     else
1143     {
1144       DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1145     }
1146   }
1147   else
1148   {
1149     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1150   }
1151 }
1152
1153 // Linear sampling group below
1154
1155 namespace
1156 {
1157
1158 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1159 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1160 {
1161   return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1162 }
1163
1164 /** @copydoc BilinearFilter1BPPByte */
1165 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1166 {
1167   Pixel2Bytes pixel;
1168   pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
1169   pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1170   return pixel;
1171 }
1172
1173 /** @copydoc BilinearFilter1BPPByte */
1174 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1175 {
1176   Pixel3Bytes pixel;
1177   pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1178   pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1179   pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1180   return pixel;
1181 }
1182
1183 /** @copydoc BilinearFilter1BPPByte */
1184 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1185 {
1186   const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1187                             (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1188                              BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
1189   return pixel;
1190 }
1191
1192 /** @copydoc BilinearFilter1BPPByte */
1193 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1194 {
1195   Pixel4Bytes pixel;
1196   pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1197   pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1198   pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1199   pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1200   return pixel;
1201 }
1202
1203 /**
1204  * @brief Generic version of bilinear sampling image resize function.
1205  * @note Limited to one compilation unit and exposed through type-specific
1206  * wrapper functions below.
1207  */
1208 template<
1209   typename PIXEL,
1210   PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1211   bool DEBUG_ASSERT_ALIGNMENT
1212 >
1213 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1214                        ImageDimensions inputDimensions,
1215                        unsigned char * __restrict__ outPixels,
1216                        ImageDimensions desiredDimensions )
1217 {
1218   const unsigned int inputWidth = inputDimensions.GetWidth();
1219   const unsigned int inputHeight = inputDimensions.GetHeight();
1220   const unsigned int desiredWidth = desiredDimensions.GetWidth();
1221   const unsigned int desiredHeight = desiredDimensions.GetHeight();
1222
1223   DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth   * inputHeight   * sizeof(PIXEL)) ||
1224                       (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1225                      "Input and output buffers cannot overlap.");
1226   if( DEBUG_ASSERT_ALIGNMENT )
1227   {
1228     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, ...)." );
1229     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, ...)." );
1230   }
1231
1232   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1233   {
1234     return;
1235   }
1236   const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1237   PIXEL* const       outAligned = reinterpret_cast<PIXEL*>(outPixels);
1238   const unsigned int deltaX = (inputWidth  << 16u) / desiredWidth;
1239   const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1240
1241   unsigned int inY = 0;
1242   for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1243   {
1244     PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1245
1246     // Find the two scanlines to blend and the weight to blend with:
1247     const unsigned int integerY1 = inY >> 16u;
1248     const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1249     const unsigned int inputYWeight = inY & 65535u;
1250
1251     DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1252     DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1253
1254     const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1255     const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1256
1257     unsigned int inX = 0;
1258     for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1259     {
1260       // Work out the two pixel scanline offsets for this cluster of four samples:
1261       const unsigned int integerX1 = inX >> 16u;
1262       const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1263
1264       // Execute the loads:
1265       const PIXEL pixel1 = inScanline1[integerX1];
1266       const PIXEL pixel2 = inScanline2[integerX1];
1267       const PIXEL pixel3 = inScanline1[integerX2];
1268       const PIXEL pixel4 = inScanline2[integerX2];
1269       ///@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.
1270
1271       // Weighted bilinear filter:
1272       const unsigned int inputXWeight = inX & 65535u;
1273       outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1274
1275       inX += deltaX;
1276     }
1277     inY += deltaY;
1278   }
1279 }
1280
1281 }
1282
1283 // Format-specific linear scaling instantiations:
1284
1285 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1286                        ImageDimensions inputDimensions,
1287                        unsigned char * __restrict__ outPixels,
1288                        ImageDimensions desiredDimensions )
1289 {
1290   LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1291 }
1292
1293 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1294                        ImageDimensions inputDimensions,
1295                        unsigned char * __restrict__ outPixels,
1296                        ImageDimensions desiredDimensions )
1297 {
1298   LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1299 }
1300
1301 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1302                        ImageDimensions inputDimensions,
1303                        unsigned char * __restrict__ outPixels,
1304                        ImageDimensions desiredDimensions )
1305 {
1306   LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1307 }
1308
1309 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1310                        ImageDimensions inputDimensions,
1311                        unsigned char * __restrict__ outPixels,
1312                        ImageDimensions desiredDimensions )
1313 {
1314   LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1315 }
1316
1317 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1318                        ImageDimensions inputDimensions,
1319                        unsigned char * __restrict__ outPixels,
1320                        ImageDimensions desiredDimensions )
1321 {
1322   LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1323 }
1324
1325 // Dispatch to a format-appropriate linear sampling function:
1326 void LinearSample( const unsigned char * __restrict__ inPixels,
1327                    ImageDimensions inDimensions,
1328                    Pixel::Format pixelFormat,
1329                    unsigned char * __restrict__ outPixels,
1330                    ImageDimensions outDimensions )
1331 {
1332   // Check the pixel format is one that is supported:
1333   if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
1334   {
1335     if( pixelFormat == Pixel::RGB888 )
1336     {
1337       LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
1338     }
1339     else if( pixelFormat == Pixel::RGBA8888 )
1340     {
1341       LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
1342     }
1343     else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1344     {
1345       LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
1346     }
1347     else if( pixelFormat == Pixel::LA88 )
1348     {
1349       LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
1350     }
1351     else if ( pixelFormat == Pixel::RGB565 )
1352     {
1353       LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
1354     }
1355     else
1356     {
1357       DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1358     }
1359   }
1360   else
1361   {
1362     DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1363   }
1364 }
1365
1366 } /* namespace Platform */
1367 } /* namespace Internal */
1368 } /* namespace Dali */