Dali 3D User Interface Engine
image-operations.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015 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 <cmath>
26 
27 // INTERNAL INCLUDES
28 
29 namespace Dali
30 {
31 namespace Internal
32 {
33 namespace Platform
34 {
35 
36 namespace
37 {
38 
39 // The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
40 // A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
41 // We can optionally use a Vector4 color here, but at reduced fill speed.
42 const uint8_t BORDER_FILL_VALUE( 0x00 );
43 // A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
44 const unsigned int MAXIMUM_TARGET_BITMAP_SIZE( ( 1u << 16 ) - 1 );
45 
48 typedef unsigned char PixelBuffer;
49 
54 {
55  uint8_t r;
56  uint8_t g;
57  uint8_t b;
58  uint8_t a;
59 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
60 
65 {
66  uint8_t r;
67  uint8_t g;
68  uint8_t b;
69 } __attribute__((packed, aligned(1)));
70 
76 typedef uint16_t PixelRGB565;
77 
82 {
83  uint8_t l;
84  uint8_t a;
85 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
86 
87 
88 #if defined(DEBUG_ENABLED)
89 
97 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
98 #endif
99 
101 inline unsigned int EvenDown( const unsigned int a )
102 {
103  const unsigned int evened = a & ~1u;
104  return evened;
105 }
106 
110 void ValidateScalingParameters( const unsigned int inputWidth,
111  const unsigned int inputHeight,
112  const unsigned int desiredWidth,
113  const unsigned int desiredHeight )
114 {
115  if( desiredWidth > inputWidth || desiredHeight > inputHeight )
116  {
117  DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
118  }
119 
120  if( desiredWidth == 0u || desiredHeight == 0u )
121  {
122  DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
123  }
124 
125  if( inputWidth == 0u || inputHeight == 0u )
126  {
127  DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
128  }
129 }
130 
135 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
136 {
137  DALI_ASSERT_DEBUG( pixels && "Null pointer." );
138  DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
139  DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
140 }
141 
146 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
147  const uint8_t * const scanline2,
148  uint8_t* const outputScanline,
149  const size_t widthInComponents )
150 {
151  DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
152  DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
153  DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
154  DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
155  DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
156 }
157 
162 {
163  BoxDimensionTest dimensionTest;
164  dimensionTest = BoxDimensionTestEither;
165 
166  switch( fittingMode )
167  {
168  // Shrink to fit attempts to make one or zero dimensions smaller than the
169  // desired dimensions and one or two dimensions exactly the same as the desired
170  // ones, so as long as one dimension is larger than the desired size, box
171  // filtering can continue even if the second dimension is smaller than the
172  // desired dimensions:
174  {
175  dimensionTest = BoxDimensionTestEither;
176  break;
177  }
178  // Scale to fill mode keeps both dimensions at least as large as desired:
180  {
181  dimensionTest = BoxDimensionTestBoth;
182  break;
183  }
184  // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
186  {
187  dimensionTest = BoxDimensionTestX;
188  break;
189  }
190  // X Dimension is ignored by definition in FIT_HEIGHT mode:
192  {
193  dimensionTest = BoxDimensionTestY;
194  break;
195  }
196  }
197 
198  return dimensionTest;
199 }
200 
206 {
207  // Scale the input by the least extreme of the two dimensions:
208  const float widthScale = target.GetX() / float(source.GetX());
209  const float heightScale = target.GetY() / float(source.GetY());
210  const float scale = widthScale < heightScale ? widthScale : heightScale;
211 
212  // Do no scaling at all if the result would increase area:
213  if( scale >= 1.0f )
214  {
215  return source;
216  }
217 
218  return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
219 }
220 
229 {
230  DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in" );
231  // Scale the input by the least extreme of the two dimensions:
232  const float widthScale = target.GetX() / float(source.GetX());
233  const float heightScale = target.GetY() / float(source.GetY());
234  const float scale = widthScale > heightScale ? widthScale : heightScale;
235 
236  // Do no scaling at all if the result would increase area:
237  if( scale >= 1.0f )
238  {
239  return source;
240  }
241 
242  return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
243 }
244 
250 {
251  DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
252  const float scale = target.GetX() / float(source.GetX());
253 
254  // Do no scaling at all if the result would increase area:
255  if( scale >= 1.0f )
256  {
257  return source;
258  }
259  return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
260 }
261 
267 {
268  DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
269  const float scale = target.GetY() / float(source.GetY());
270 
271  // Do no scaling at all if the result would increase area:
272  if( scale >= 1.0f )
273  {
274  return source;
275  }
276 
277  return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
278 }
279 
285 {
286  ImageDimensions fitDimensions;
287  switch( fittingMode )
288  {
290  {
291  fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
292  break;
293  }
295  {
296  fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
297  break;
298  }
300  {
301  fitDimensions = FitForFitWidth( requestedSize, sourceSize );
302  break;
303  }
305  {
306  fitDimensions = FitForFitHeight( requestedSize, sourceSize );
307  break;
308  }
309  }
310 
311  return fitDimensions;
312 }
313 
326 void CalculateBordersFromFittingMode( ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
327 {
328  const unsigned int sourceWidth( sourceSize.GetWidth() );
329  const unsigned int sourceHeight( sourceSize.GetHeight() );
330  const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) );
331  int finalWidth = 0;
332  int finalHeight = 0;
333 
334  switch( fittingMode )
335  {
337  {
338  finalWidth = sourceWidth;
339  finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
340 
341  columnsToCrop = 0;
342  scanlinesToCrop = -( finalHeight - sourceHeight );
343  break;
344  }
345 
347  {
348  finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
349  finalHeight = sourceHeight;
350 
351  columnsToCrop = -( finalWidth - sourceWidth );
352  scanlinesToCrop = 0;
353  break;
354  }
355 
357  {
358  const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
359  if( sourceAspect > targetAspect )
360  {
361  finalWidth = sourceWidth;
362  finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
363 
364  columnsToCrop = 0;
365  scanlinesToCrop = -( finalHeight - sourceHeight );
366  }
367  else
368  {
369  finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
370  finalHeight = sourceHeight;
371 
372  columnsToCrop = -( finalWidth - sourceWidth );
373  scanlinesToCrop = 0;
374  }
375  break;
376  }
377 
379  {
380  const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
381  if( sourceAspect > targetAspect )
382  {
383  finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
384  finalHeight = sourceHeight;
385 
386  columnsToCrop = -( finalWidth - sourceWidth );
387  scanlinesToCrop = 0;
388  }
389  else
390  {
391  finalWidth = sourceWidth;
392  finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
393 
394  columnsToCrop = 0;
395  scanlinesToCrop = -( finalHeight - sourceHeight );
396  }
397  break;
398  }
399  }
400 
401  requestedSize.SetWidth( finalWidth );
402  requestedSize.SetHeight( finalHeight );
403 }
404 
408 BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
409 {
410  DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
411 
412  // Allocate a pixel buffer to hold the image passed in:
414  newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
415  return newBitmap;
416 }
417 
421 BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
422 {
423  DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
424 
425  // Allocate a pixel buffer to hold the image passed in:
426  Integration::BitmapPtr newBitmap = MakeEmptyBitmap( pixelFormat, width, height );
427 
428  // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
429  memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
430  return newBitmap;
431 }
432 
442 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
443 {
444  // If no dimensions have been requested, default to the source ones:
445  if( requestedWidth == 0 && requestedHeight == 0 )
446  {
447  return ImageDimensions( bitmapWidth, bitmapHeight );
448  }
449 
450  // If both dimensions have values requested, use them both:
451  if( requestedWidth != 0 && requestedHeight != 0 )
452  {
453  return ImageDimensions( requestedWidth, requestedHeight );
454  }
455 
456  // Only one of the dimensions has been requested. Calculate the other from
457  // the requested one and the source image aspect ratio:
458  if( requestedWidth != 0 )
459  {
460  return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
461  }
462  return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
463 }
464 
465 } // namespace - unnamed
466 
468 {
469  return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
470 }
471 
493 
502 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
503 
505 {
506  if( bitmap )
507  {
508  // Calculate the desired box, accounting for a possible zero component:
509  const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
510 
511  // If a different size than the raw one has been requested, resize the image
512  // maximally using a repeated box filter without making it smaller than the
513  // requested size in either dimension:
514  bitmap = DownscaleBitmap( *bitmap, desiredDimensions, fittingMode, samplingMode );
515 
516  // Cut the bitmap according to the desired width and height so that the
517  // resulting bitmap has the same aspect ratio as the desired dimensions.
518  // Add crop and add borders if necessary depending on fitting mode.
519  if( bitmap && bitmap->GetPackedPixelsProfile() )
520  {
521  bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
522  }
523 
524  // Examine the image pixels remaining after cropping and scaling to see if all
525  // are opaque, allowing faster rendering, or some have non-1.0 alpha:
526  if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
527  {
528  bitmap->GetPackedPixelsProfile()->TestForTransparency();
529  }
530  }
531 
532  return bitmap;
533 }
534 
536 {
537  const unsigned int inputWidth = bitmap->GetImageWidth();
538  const unsigned int inputHeight = bitmap->GetImageHeight();
539 
540  if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
541  {
542  DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
543  }
544  else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
545  {
546  // Calculate any padding or cropping that needs to be done based on the fitting mode.
547  // Note: If the desired size is larger than the original image, the desired size will be
548  // reduced while maintaining the aspect, in order to save unnecessary memory usage.
549  int scanlinesToCrop = 0;
550  int columnsToCrop = 0;
551 
552  CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
553 
554  unsigned int desiredWidth( desiredDimensions.GetWidth() );
555  unsigned int desiredHeight( desiredDimensions.GetHeight() );
556 
557  // Action the changes by making a new bitmap with the central part of the loaded one if required.
558  if( scanlinesToCrop != 0 || columnsToCrop != 0 )
559  {
560  // Split the adding and removing of scanlines and columns into separate variables,
561  // so we can use one piece of generic code to action the changes.
562  unsigned int scanlinesToPad = 0;
563  unsigned int columnsToPad = 0;
564  if( scanlinesToCrop < 0 )
565  {
566  scanlinesToPad = -scanlinesToCrop;
567  scanlinesToCrop = 0;
568  }
569  if( columnsToCrop < 0 )
570  {
571  columnsToPad = -columnsToCrop;
572  columnsToCrop = 0;
573  }
574 
575  // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
576  if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
577  ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
578  {
579  DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
580  return bitmap;
581  }
582 
583  // Create a new bitmap with the desired size.
586  DALI_ASSERT_DEBUG( packedView );
587  const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
588  packedView->ReserveBuffer( pixelFormat, desiredWidth, desiredHeight, desiredWidth, desiredHeight );
589 
590  // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
591  // The cropping is added to the source pointer, and the padding is added to the destination.
592  const unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
593  const PixelBuffer * const sourcePixels = bitmap->GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
594  PixelBuffer * const targetPixels = croppedBitmap->GetBuffer();
595  PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
596  DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
597 
598  // Copy the image data to the new bitmap.
599  // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
600  unsigned int outputSpan( desiredWidth * bytesPerPixel );
601  if( columnsToCrop == 0 && columnsToPad == 0 )
602  {
603  memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
604  }
605  else
606  {
607  // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
608  // Precalculate any constants to optimize the inner loop.
609  const unsigned int inputSpan( inputWidth * bytesPerPixel );
610  const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
611  const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
612 
613  for( unsigned int y = 0; y < scanlinesToCopy; ++y )
614  {
615  memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
616  }
617  }
618 
619  // Add vertical or horizontal borders to the final image (if required).
620  desiredDimensions.SetWidth( desiredWidth );
621  desiredDimensions.SetHeight( desiredHeight );
622  AddBorders( croppedBitmap->GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
623  // Overwrite the loaded bitmap with the cropped version
624  bitmap = croppedBitmap;
625  }
626  }
627 
628  return bitmap;
629 }
630 
631 void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
632 {
633  // Assign ints for faster access.
634  unsigned int desiredWidth( targetDimensions.GetWidth() );
635  unsigned int desiredHeight( targetDimensions.GetHeight() );
636  unsigned int columnsToPad( padDimensions.GetWidth() );
637  unsigned int scanlinesToPad( padDimensions.GetHeight() );
638  unsigned int outputSpan( desiredWidth * bytesPerPixel );
639 
640  // Add letterboxing (symmetrical borders) if needed.
641  if( scanlinesToPad > 0 )
642  {
643  // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
644  memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
645 
646  // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
647  // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
648  unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
649 
650  // Bottom border.
651  memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
652  }
653  else if( columnsToPad > 0 )
654  {
655  // Add a left and right border.
656  // Left:
657  // Pre-calculate span size outside of loop.
658  unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
659  for( unsigned int y = 0; y < desiredHeight; ++y )
660  {
661  memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
662  }
663 
664  // Right:
665  // Pre-calculate the initial x offset as it is always the same for a small optimization.
666  // We subtract columnsToPad/2 from columnsToPad so that we have the correct
667  // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
668  unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
669  PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
670  unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
671 
672  for( unsigned int y = 0; y < desiredHeight; ++y )
673  {
674  memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
675  }
676  }
677 }
678 
680  ImageDimensions desired,
681  FittingMode::Type fittingMode,
682  SamplingMode::Type samplingMode )
683 {
684  // Source dimensions as loaded from resources (e.g. filesystem):
685  const unsigned int bitmapWidth = bitmap.GetImageWidth();
686  const unsigned int bitmapHeight = bitmap.GetImageHeight();
687  // Desired dimensions (the rectangle to fit the source image to):
688  const unsigned int desiredWidth = desired.GetWidth();
689  const unsigned int desiredHeight = desired.GetHeight();
690 
691  BitmapPtr outputBitmap( &bitmap );
692 
693  // If a different size than the raw one has been requested, resize the image:
694  if( bitmap.GetPackedPixelsProfile() &&
695  (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
696  ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
697  {
698  const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
699 
700  // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
701  unsigned int shrunkWidth = -1, shrunkHeight = -1;
702  DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight );
703 
704  // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
705  const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), fittingMode );
706  const unsigned int filteredWidth = filteredDimensions.GetWidth();
707  const unsigned int filteredHeight = filteredDimensions.GetHeight();
708 
709  // Run a filter to scale down the bitmap if it needs it:
710  bool filtered = false;
711  if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
712  {
713  if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
714  samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
715  {
716  outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
717  if( outputBitmap )
718  {
719  if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
720  {
721  LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
722  }
723  else
724  {
725  PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap->GetBuffer(), filteredWidth, filteredHeight );
726  }
727  filtered = true;
728  }
729  }
730  }
731  // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
732  if( filtered == false && ( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight ) )
733  {
734  outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
735  }
736  }
737 
738  return outputBitmap;
739 }
740 
741 namespace
742 {
751 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
752 {
753  bool keepScaling = false;
754  const unsigned int nextWidth = scaledWidth >> 1u;
755  const unsigned int nextHeight = scaledHeight >> 1u;
756 
757  if( nextWidth >= 1u && nextHeight >= 1u )
758  {
759  switch( test )
760  {
762  {
763  keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
764  break;
765  }
767  {
768  keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
769  break;
770  }
771  case BoxDimensionTestX:
772  {
773  keepScaling = nextWidth >= desiredWidth;
774  break;
775  }
776  case BoxDimensionTestY:
777  {
778  keepScaling = nextHeight >= desiredHeight;
779  break;
780  }
781  }
782  }
783 
784  return keepScaling;
785 }
786 
796 template<
797  int BYTES_PER_PIXEL,
798  void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
799  void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
800 >
801 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
802  const unsigned int inputWidth,
803  const unsigned int inputHeight,
804  const unsigned int desiredWidth,
805  const unsigned int desiredHeight,
806  BoxDimensionTest dimensionTest,
807  unsigned& outWidth,
808  unsigned& outHeight )
809 {
810  if( pixels == 0 )
811  {
812  return;
813  }
814  ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
815 
816  // Scale the image until it would be smaller than desired, stopping if the
817  // resulting height or width would be less than 1:
818  unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
819  while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
820  {
821  const unsigned int lastWidth = scaledWidth;
822  scaledWidth >>= 1u;
823  scaledHeight >>= 1u;
824 
825  DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
826 
827  const unsigned int lastScanlinePair = scaledHeight - 1;
828 
829  // Scale pairs of scanlines until any spare one at the end is dropped:
830  for( unsigned int y = 0; y <= lastScanlinePair; ++y )
831  {
832  // Scale two scanlines horizontally:
833  HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
834  HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
835 
836  // Scale vertical pairs of pixels while the last two scanlines are still warm in
837  // the CPU cache(s):
838  // Note, better access patterns for cache-coherence are possible for very large
839  // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
840  // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
841  AverageScanlines(
842  &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
843  &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
844  &pixels[y * scaledWidth * BYTES_PER_PIXEL],
845  scaledWidth );
846  }
847  }
848 
850  outWidth = scaledWidth;
851  outHeight = scaledHeight;
852 }
853 
854 }
855 
856 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
857 {
858  DebugAssertScanlineParameters( pixels, width );
859 
860  const unsigned int lastPair = EvenDown( width - 2 );
861 
862  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
863  {
864  // Load all the byte pixel components we need:
865  const unsigned int c11 = pixels[pixel * 3];
866  const unsigned int c12 = pixels[pixel * 3 + 1];
867  const unsigned int c13 = pixels[pixel * 3 + 2];
868  const unsigned int c21 = pixels[pixel * 3 + 3];
869  const unsigned int c22 = pixels[pixel * 3 + 4];
870  const unsigned int c23 = pixels[pixel * 3 + 5];
871 
872  // Save the averaged byte pixel components:
873  pixels[outPixel * 3] = AverageComponent( c11, c21 );
874  pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
875  pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
876  }
877 }
878 
879 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
880 {
881  DebugAssertScanlineParameters( pixels, width );
882  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
883 
884  uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
885 
886  const unsigned int lastPair = EvenDown( width - 2 );
887 
888  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
889  {
890  const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
891  alignedPixels[outPixel] = averaged;
892  }
893 }
894 
895 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
896 {
897  DebugAssertScanlineParameters( pixels, width );
898  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
899 
900  uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
901 
902  const unsigned int lastPair = EvenDown( width - 2 );
903 
904  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
905  {
906  const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
907  alignedPixels[outPixel] = averaged;
908  }
909 }
910 
911 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
912 {
913  DebugAssertScanlineParameters( pixels, width );
914 
915  const unsigned int lastPair = EvenDown( width - 2 );
916 
917  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
918  {
919  // Load all the byte pixel components we need:
920  const unsigned int c11 = pixels[pixel * 2];
921  const unsigned int c12 = pixels[pixel * 2 + 1];
922  const unsigned int c21 = pixels[pixel * 2 + 2];
923  const unsigned int c22 = pixels[pixel * 2 + 3];
924 
925  // Save the averaged byte pixel components:
926  pixels[outPixel * 2] = AverageComponent( c11, c21 );
927  pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
928  }
929 }
930 
931 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
932 {
933  DebugAssertScanlineParameters( pixels, width );
934 
935  const unsigned int lastPair = EvenDown( width - 2 );
936 
937  for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
938  {
939  // Load all the byte pixel components we need:
940  const unsigned int c1 = pixels[pixel];
941  const unsigned int c2 = pixels[pixel + 1];
942 
943  // Save the averaged byte pixel component:
944  pixels[outPixel] = AverageComponent( c1, c2 );
945  }
946 }
947 
952 void AverageScanlines1( const unsigned char * const scanline1,
953  const unsigned char * const __restrict__ scanline2,
954  unsigned char* const outputScanline,
955  const unsigned int width )
956 {
957  DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
958 
959  for( unsigned int component = 0; component < width; ++component )
960  {
961  outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
962  }
963 }
964 
965 void AverageScanlines2( const unsigned char * const scanline1,
966  const unsigned char * const __restrict__ scanline2,
967  unsigned char* const outputScanline,
968  const unsigned int width )
969 {
970  DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
971 
972  for( unsigned int component = 0; component < width * 2; ++component )
973  {
974  outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
975  }
976 }
977 
978 void AverageScanlines3( const unsigned char * const scanline1,
979  const unsigned char * const __restrict__ scanline2,
980  unsigned char* const outputScanline,
981  const unsigned int width )
982 {
983  DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
984 
985  for( unsigned int component = 0; component < width * 3; ++component )
986  {
987  outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
988  }
989 }
990 
991 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
992  const unsigned char * const __restrict__ scanline2,
993  unsigned char * const outputScanline,
994  const unsigned int width )
995 {
996  DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
997  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
998  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
999  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1000 
1001  const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1002  const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1003  uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1004 
1005  for( unsigned int pixel = 0; pixel < width; ++pixel )
1006  {
1007  alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
1008  }
1009 }
1010 
1011 void AverageScanlinesRGB565( const unsigned char * const scanline1,
1012  const unsigned char * const __restrict__ scanline2,
1013  unsigned char * const outputScanline,
1014  const unsigned int width )
1015 {
1016  DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
1017  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1018  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1019  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
1020 
1021  const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1022  const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1023  uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1024 
1025  for( unsigned int pixel = 0; pixel < width; ++pixel )
1026  {
1027  alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
1028  }
1029 }
1030 
1032 void DownscaleInPlacePow2( unsigned char * const pixels,
1033  Pixel::Format pixelFormat,
1034  unsigned int inputWidth,
1035  unsigned int inputHeight,
1036  unsigned int desiredWidth,
1037  unsigned int desiredHeight,
1038  FittingMode::Type fittingMode,
1039  SamplingMode::Type samplingMode,
1040  unsigned& outWidth,
1041  unsigned& outHeight )
1042 {
1043  outWidth = inputWidth;
1044  outHeight = inputHeight;
1045  // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1046  if( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
1047  {
1048  // Check the pixel format is one that is supported:
1049  if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1050  {
1051  const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
1052 
1053  if( pixelFormat == Pixel::RGBA8888 )
1054  {
1055  Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1056  }
1057  else if( pixelFormat == Pixel::RGB888 )
1058  {
1059  Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1060  }
1061  else if( pixelFormat == Pixel::RGB565 )
1062  {
1063  Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1064  }
1065  else if( pixelFormat == Pixel::LA88 )
1066  {
1067  Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1068  }
1069  else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1070  {
1071  Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1072  }
1073  else
1074  {
1075  DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1076  }
1077  }
1078  }
1079  else
1080  {
1081  DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1082  }
1083 }
1084 
1085 void DownscaleInPlacePow2RGB888( unsigned char *pixels,
1086  unsigned int inputWidth,
1087  unsigned int inputHeight,
1088  unsigned int desiredWidth,
1089  unsigned int desiredHeight,
1090  BoxDimensionTest dimensionTest,
1091  unsigned& outWidth,
1092  unsigned& outHeight )
1093 {
1094  DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1095 }
1096 
1097 void DownscaleInPlacePow2RGBA8888( unsigned char * pixels,
1098  unsigned int inputWidth,
1099  unsigned int inputHeight,
1100  unsigned int desiredWidth,
1101  unsigned int desiredHeight,
1102  BoxDimensionTest dimensionTest,
1103  unsigned& outWidth,
1104  unsigned& outHeight )
1105 {
1106  DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
1107  DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1108 }
1109 
1110 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
1111  unsigned int inputWidth,
1112  unsigned int inputHeight,
1113  unsigned int desiredWidth,
1114  unsigned int desiredHeight,
1115  BoxDimensionTest dimensionTest,
1116  unsigned int& outWidth,
1117  unsigned int& outHeight )
1118 {
1119  DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1120 }
1121 
1127 void DownscaleInPlacePow2ComponentPair( unsigned char *pixels,
1128  unsigned int inputWidth,
1129  unsigned int inputHeight,
1130  unsigned int desiredWidth,
1131  unsigned int desiredHeight,
1132  BoxDimensionTest dimensionTest,
1133  unsigned& outWidth,
1134  unsigned& outHeight )
1135 {
1136  DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1137 }
1138 
1139 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
1140  unsigned int inputWidth,
1141  unsigned int inputHeight,
1142  unsigned int desiredWidth,
1143  unsigned int desiredHeight,
1144  BoxDimensionTest dimensionTest,
1145  unsigned int& outWidth,
1146  unsigned int& outHeight )
1147 {
1148  DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
1149 }
1150 
1151 namespace
1152 {
1153 
1161 template<typename PIXEL>
1162 inline void PointSampleAddressablePixels( const uint8_t * inPixels,
1163  unsigned int inputWidth,
1164  unsigned int inputHeight,
1165  uint8_t * outPixels,
1166  unsigned int desiredWidth,
1167  unsigned int desiredHeight )
1168 {
1169  DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1170  outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1171  "The input and output buffers must not overlap for an upscaling.");
1172  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, ...)." );
1173  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, ...)." );
1174 
1175  if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1176  {
1177  return;
1178  }
1179  const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1180  PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1181  const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1182  const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1183 
1184  unsigned int inY = 0;
1185  for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1186  {
1187  // Round fixed point y coordinate to nearest integer:
1188  const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1189  const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1190  PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1191 
1192  DALI_ASSERT_DEBUG( integerY < inputHeight );
1193  DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
1194  DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
1195 
1196  unsigned int inX = 0;
1197  for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1198  {
1199  // Round the fixed-point x coordinate to an integer:
1200  const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1201  const PIXEL* const inPixelAddress = &inScanline[integerX];
1202  const PIXEL pixel = *inPixelAddress;
1203  outScanline[outX] = pixel;
1204  inX += deltaX;
1205  }
1206  inY += deltaY;
1207  }
1208 }
1209 
1210 }
1211 
1212 // RGBA8888
1213 void PointSample4BPP( const unsigned char * inPixels,
1214  unsigned int inputWidth,
1215  unsigned int inputHeight,
1216  unsigned char * outPixels,
1217  unsigned int desiredWidth,
1218  unsigned int desiredHeight )
1219 {
1220  PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1221 }
1222 
1223 // RGB565, LA88
1224 void PointSample2BPP( const unsigned char * inPixels,
1225  unsigned int inputWidth,
1226  unsigned int inputHeight,
1227  unsigned char * outPixels,
1228  unsigned int desiredWidth,
1229  unsigned int desiredHeight )
1230 {
1231  PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1232 }
1233 
1234 // L8, A8
1235 void PointSample1BPP( const unsigned char * inPixels,
1236  unsigned int inputWidth,
1237  unsigned int inputHeight,
1238  unsigned char * outPixels,
1239  unsigned int desiredWidth,
1240  unsigned int desiredHeight )
1241 {
1242  PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1243 }
1244 
1245 /* RGB888
1246  * RGB888 is a special case as its pixels are not aligned addressable units.
1247  */
1248 void PointSample3BPP( const uint8_t * inPixels,
1249  unsigned int inputWidth,
1250  unsigned int inputHeight,
1251  uint8_t * outPixels,
1252  unsigned int desiredWidth,
1253  unsigned int desiredHeight )
1254 {
1255  if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1256  {
1257  return;
1258  }
1259  const unsigned int BYTES_PER_PIXEL = 3;
1260 
1261  // Generate fixed-point 16.16 deltas in input image coordinates:
1262  const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1263  const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1264 
1265  // Step through output image in whole integer pixel steps while tracking the
1266  // corresponding locations in the input image using 16.16 fixed-point
1267  // coordinates:
1268  unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1269  for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1270  {
1271  const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1272  const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1273  uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1274  unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1275 
1276  for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1277  {
1278  // Round the fixed-point input coordinate to the address of the input pixel to sample:
1279  const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1280  const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1281 
1282  // Issue loads for all pixel color components up-front:
1283  const unsigned int c0 = inPixelAddress[0];
1284  const unsigned int c1 = inPixelAddress[1];
1285  const unsigned int c2 = inPixelAddress[2];
1287 
1288  // Output the pixel components:
1289  outScanline[outX] = c0;
1290  outScanline[outX + 1] = c1;
1291  outScanline[outX + 2] = c2;
1292 
1293  // Increment the fixed-point input coordinate:
1294  inX += deltaX;
1295  }
1296 
1297  inY += deltaY;
1298  }
1299 }
1300 
1301 // Dispatch to a format-appropriate point sampling function:
1302 void PointSample( const unsigned char * inPixels,
1303  unsigned int inputWidth,
1304  unsigned int inputHeight,
1305  Pixel::Format pixelFormat,
1306  unsigned char * outPixels,
1307  unsigned int desiredWidth,
1308  unsigned int desiredHeight )
1309 {
1310  // Check the pixel format is one that is supported:
1311  if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1312  {
1313  if( pixelFormat == Pixel::RGB888 )
1314  {
1315  PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1316  }
1317  else if( pixelFormat == Pixel::RGBA8888 )
1318  {
1319  PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1320  }
1321  else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1322  {
1323  PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1324  }
1325  else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1326  {
1327  PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1328  }
1329  else
1330  {
1331  DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1332  }
1333  }
1334  else
1335  {
1336  DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1337  }
1338 }
1339 
1340 // Linear sampling group below
1341 
1342 namespace
1343 {
1344 
1346 inline uint8_t BilinearFilter1BPPByte( uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1347 {
1348  return BilinearFilter1Component( tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical );
1349 }
1350 
1352 inline Pixel2Bytes BilinearFilter2Bytes( Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1353 {
1354  Pixel2Bytes pixel;
1355  pixel.l = BilinearFilter1Component( tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical );
1356  pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1357  return pixel;
1358 }
1359 
1361 inline Pixel3Bytes BilinearFilterRGB888( Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1362 {
1363  Pixel3Bytes pixel;
1364  pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1365  pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1366  pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1367  return pixel;
1368 }
1369 
1371 inline PixelRGB565 BilinearFilterRGB565( PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1372 {
1373  const PixelRGB565 pixel = (BilinearFilter1Component( tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical ) << 11u) +
1374  (BilinearFilter1Component( (tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical ) << 5u) +
1375  BilinearFilter1Component( tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical );
1376  return pixel;
1377 }
1378 
1380 inline Pixel4Bytes BilinearFilter4Bytes( Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical )
1381 {
1382  Pixel4Bytes pixel;
1383  pixel.r = BilinearFilter1Component( tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical );
1384  pixel.g = BilinearFilter1Component( tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical );
1385  pixel.b = BilinearFilter1Component( tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical );
1386  pixel.a = BilinearFilter1Component( tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical );
1387  return pixel;
1388 }
1389 
1395 template<
1396  typename PIXEL,
1397  PIXEL (*BilinearFilter) ( PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical ),
1398  bool DEBUG_ASSERT_ALIGNMENT
1399 >
1400 inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
1401  ImageDimensions inputDimensions,
1402  unsigned char * __restrict__ outPixels,
1403  ImageDimensions desiredDimensions )
1404 {
1405  const unsigned int inputWidth = inputDimensions.GetWidth();
1406  const unsigned int inputHeight = inputDimensions.GetHeight();
1407  const unsigned int desiredWidth = desiredDimensions.GetWidth();
1408  const unsigned int desiredHeight = desiredDimensions.GetHeight();
1409 
1410  DALI_ASSERT_DEBUG( ((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
1411  (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1412  "Input and output buffers cannot overlap.");
1413  if( DEBUG_ASSERT_ALIGNMENT )
1414  {
1415  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, ...)." );
1416  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, ...)." );
1417  }
1418 
1419  if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1420  {
1421  return;
1422  }
1423  const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1424  PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1425  const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1426  const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1427 
1428  unsigned int inY = 0;
1429  for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1430  {
1431  PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1432 
1433  // Find the two scanlines to blend and the weight to blend with:
1434  const unsigned int integerY1 = inY >> 16u;
1435  const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1436  const unsigned int inputYWeight = inY & 65535u;
1437 
1438  DALI_ASSERT_DEBUG( integerY1 < inputHeight );
1439  DALI_ASSERT_DEBUG( integerY2 < inputHeight );
1440 
1441  const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1442  const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1443 
1444  unsigned int inX = 0;
1445  for( unsigned int outX = 0; outX < desiredWidth; ++outX )
1446  {
1447  // Work out the two pixel scanline offsets for this cluster of four samples:
1448  const unsigned int integerX1 = inX >> 16u;
1449  const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1450 
1451  // Execute the loads:
1452  const PIXEL pixel1 = inScanline1[integerX1];
1453  const PIXEL pixel2 = inScanline2[integerX1];
1454  const PIXEL pixel3 = inScanline1[integerX2];
1455  const PIXEL pixel4 = inScanline2[integerX2];
1457 
1458  // Weighted bilinear filter:
1459  const unsigned int inputXWeight = inX & 65535u;
1460  outScanline[outX] = BilinearFilter( pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight );
1461 
1462  inX += deltaX;
1463  }
1464  inY += deltaY;
1465  }
1466 }
1467 
1468 }
1469 
1470 // Format-specific linear scaling instantiations:
1471 
1472 void LinearSample1BPP( const unsigned char * __restrict__ inPixels,
1473  ImageDimensions inputDimensions,
1474  unsigned char * __restrict__ outPixels,
1475  ImageDimensions desiredDimensions )
1476 {
1477  LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1478 }
1479 
1480 void LinearSample2BPP( const unsigned char * __restrict__ inPixels,
1481  ImageDimensions inputDimensions,
1482  unsigned char * __restrict__ outPixels,
1483  ImageDimensions desiredDimensions )
1484 {
1485  LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1486 }
1487 
1488 void LinearSampleRGB565( const unsigned char * __restrict__ inPixels,
1489  ImageDimensions inputDimensions,
1490  unsigned char * __restrict__ outPixels,
1491  ImageDimensions desiredDimensions )
1492 {
1493  LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1494 }
1495 
1496 void LinearSample3BPP( const unsigned char * __restrict__ inPixels,
1497  ImageDimensions inputDimensions,
1498  unsigned char * __restrict__ outPixels,
1499  ImageDimensions desiredDimensions )
1500 {
1501  LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>( inPixels, inputDimensions, outPixels, desiredDimensions );
1502 }
1503 
1504 void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
1505  ImageDimensions inputDimensions,
1506  unsigned char * __restrict__ outPixels,
1507  ImageDimensions desiredDimensions )
1508 {
1509  LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
1510 }
1511 
1512 // Dispatch to a format-appropriate linear sampling function:
1513 void LinearSample( const unsigned char * __restrict__ inPixels,
1514  ImageDimensions inDimensions,
1515  Pixel::Format pixelFormat,
1516  unsigned char * __restrict__ outPixels,
1517  ImageDimensions outDimensions )
1518 {
1519  // Check the pixel format is one that is supported:
1520  if( pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 )
1521  {
1522  if( pixelFormat == Pixel::RGB888 )
1523  {
1524  LinearSample3BPP( inPixels, inDimensions, outPixels, outDimensions );
1525  }
1526  else if( pixelFormat == Pixel::RGBA8888 )
1527  {
1528  LinearSample4BPP( inPixels, inDimensions, outPixels, outDimensions );
1529  }
1530  else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1531  {
1532  LinearSample1BPP( inPixels, inDimensions, outPixels, outDimensions );
1533  }
1534  else if( pixelFormat == Pixel::LA88 )
1535  {
1536  LinearSample2BPP( inPixels, inDimensions, outPixels, outDimensions );
1537  }
1538  else if ( pixelFormat == Pixel::RGB565 )
1539  {
1540  LinearSampleRGB565( inPixels, inDimensions, outPixels, outDimensions );
1541  }
1542  else
1543  {
1544  DALI_ASSERT_DEBUG( false == "Inner branch conditions don't match outer branch." );
1545  }
1546  }
1547  else
1548  {
1549  DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1550  }
1551 }
1552 
1553 } /* namespace Platform */
1554 } /* namespace Internal */
1555 } /* namespace Dali */
Dali Docs Home
Read more about Dali