Changed alpha mask scaling to use Lanczos.
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / portable / image-operations.cpp
index 7ccfa55..f759d39 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 // EXTERNAL INCLUDES
 #include <cstring>
 #include <stddef.h>
+#include <cmath>
+#include <limits>
 #include <dali/integration-api/debug.h>
+#include <dali/public-api/common/dali-vector.h>
+#include <dali/public-api/math/vector2.h>
+#include <resampler.h>
+#include <image-loading.h>
 
 // INTERNAL INCLUDES
 
@@ -34,6 +40,18 @@ namespace Platform
 namespace
 {
 
+// The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
+// A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
+// We can optionally use a Vector4 color here, but at reduced fill speed.
+const uint8_t BORDER_FILL_VALUE( 0x00 );
+// A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
+const unsigned int MAXIMUM_TARGET_BITMAP_SIZE( ( 1u << 16 ) - 1 );
+
+// Constants used by the ImageResampler.
+const float DEFAULT_SOURCE_GAMMA = 1.75f;   ///< Default source gamma value used in the Resampler() function. Partial gamma correction looks better on mips. Set to 1.0 to disable gamma correction.
+const float FILTER_SCALE = 1.f;             ///< Default filter scale value used in the Resampler() function. Filter scale - values < 1.0 cause aliasing, but create sharper looking mips.
+const Resampler::Filter FILTER_TYPE = Resampler::LANCZOS4; ///< Default filter used in the Resampler() function. Possible Lanczos filters are: lanczos3, lanczos4, lanczos6, lanczos12
+
 using Integration::Bitmap;
 using Integration::BitmapPtr;
 typedef unsigned char PixelBuffer;
@@ -110,12 +128,12 @@ void ValidateScalingParameters( const unsigned int inputWidth,
 
   if( desiredWidth == 0u || desiredHeight == 0u )
   {
-    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
+    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n" );
   }
 
   if( inputWidth == 0u || inputHeight == 0u )
   {
-    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
+    DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n" );
   }
 }
 
@@ -143,77 +161,43 @@ inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
   DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
   DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
   DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
-  DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
-}
-
-/**
- * @brief Work out the desired width and height according to the rules documented for the ImageAttributes class.
- *
- * @param[in] bitmapWidth Width of image before processing.
- * @param[in] bitmapHeight Height of image before processing.
- * @param[in] requestedWidth Width of area to scale image into. Can be zero.
- * @param[in] requestedHeight Height of area to scale image into. Can be zero.
- * @return Dimensions of area to scale image into after special rules are applied.
- * @see ImageAttributes
- */
-ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
-{
-  // If no dimensions have been requested, default to the source ones:
-  if( requestedWidth == 0 && requestedHeight == 0 )
-  {
-    return ImageDimensions( bitmapWidth, bitmapHeight );
-  }
-
-  // If both dimensions have values requested, use them both:
-  if( requestedWidth != 0 && requestedHeight != 0 )
-  {
-    return ImageDimensions( requestedWidth, requestedHeight );
-  }
-
-  // If only one of the dimensions has been requested, calculate the other from
-  // the requested one and the source image aspect ratio:
-
-  if( requestedWidth != 0 )
-  {
-    return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
-  }
-  return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
+  DALI_ASSERT_DEBUG( ((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
 }
 
 /**
  * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
  */
-BoxDimensionTest DimensionTestForScalingMode( ImageAttributes::ScalingMode scalingMode )
+BoxDimensionTest DimensionTestForScalingMode( FittingMode::Type fittingMode )
 {
   BoxDimensionTest dimensionTest;
   dimensionTest = BoxDimensionTestEither;
 
-  switch( scalingMode )
+  switch( fittingMode )
   {
     // Shrink to fit attempts to make one or zero dimensions smaller than the
     // desired dimensions and one or two dimensions exactly the same as the desired
     // ones, so as long as one dimension is larger than the desired size, box
     // filtering can continue even if the second dimension is smaller than the
     // desired dimensions:
-    case ImageAttributes::ShrinkToFit:
+    case FittingMode::SHRINK_TO_FIT:
     {
       dimensionTest = BoxDimensionTestEither;
       break;
     }
     // Scale to fill mode keeps both dimensions at least as large as desired:
-    case ImageAttributes::ScaleToFill:
+    case FittingMode::SCALE_TO_FILL:
     {
       dimensionTest = BoxDimensionTestBoth;
       break;
     }
-    // Y dimension is irrelevant when downscaling in FitWidth mode:
-    case ImageAttributes::FitWidth:
+    // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
+    case FittingMode::FIT_WIDTH:
     {
       dimensionTest = BoxDimensionTestX;
       break;
     }
-    // X Dimension is ignored by definition in FitHeight mode:
-    case ImageAttributes::FitHeight:
+    // X Dimension is ignored by definition in FIT_HEIGHT mode:
+    case FittingMode::FIT_HEIGHT:
     {
       dimensionTest = BoxDimensionTestY;
       break;
@@ -229,7 +213,6 @@ BoxDimensionTest DimensionTestForScalingMode( ImageAttributes::ScalingMode scali
  */
 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
 {
-  DALI_ASSERT_DEBUG( true && " " );
   // Scale the input by the least extreme of the two dimensions:
   const float widthScale  = target.GetX() / float(source.GetX());
   const float heightScale = target.GetY() / float(source.GetY());
@@ -246,10 +229,10 @@ ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions sourc
 
 /**
  * @brief Work out the dimensions for a uniform scaling of the input to map it
- * into the target while effecting ScaleToFill scaling mode.
- * @note The output dimensions will need either top and bottom or left and right
- * to be cropped away unless the source was pre-cropped to match the destination
- * aspect ratio.
+ * into the target while effecting SCALE_TO_FILL scaling mode.
+ * @note An image scaled into the output dimensions will need either top and
+ * bottom or left and right to be cropped away unless the source was pre-cropped
+ * to match the destination aspect ratio.
  */
 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
 {
@@ -270,7 +253,7 @@ ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions sourc
 
 /**
  * @brief Work out the dimensions for a uniform scaling of the input to map it
- * into the target while effecting FitWidth scaling mode.
+ * into the target while effecting FIT_WIDTH scaling mode.
  */
 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
 {
@@ -287,7 +270,7 @@ ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
 
 /**
  * @brief Work out the dimensions for a uniform scaling of the input to map it
- * into the target while effecting FitHeight scaling mode.
+ * into the target while effecting FIT_HEIGHT scaling mode.
  */
 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
 {
@@ -307,37 +290,128 @@ ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source
  * @brief Generate the rectangle to use as the target of a pixel sampling pass
  * (e.g., nearest or linear).
  */
-ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, ImageAttributes::ScalingMode scalingMode )
+ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode )
 {
   ImageDimensions fitDimensions;
-  switch( scalingMode )
+  switch( fittingMode )
   {
-    case ImageAttributes::ShrinkToFit:
+    case FittingMode::SHRINK_TO_FIT:
     {
       fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
       break;
     }
-    case ImageAttributes::ScaleToFill:
+    case FittingMode::SCALE_TO_FILL:
     {
       fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
       break;
     }
-    case ImageAttributes::FitWidth:
+    case FittingMode::FIT_WIDTH:
     {
       fitDimensions = FitForFitWidth( requestedSize, sourceSize );
       break;
     }
-    case ImageAttributes::FitHeight:
+    case FittingMode::FIT_HEIGHT:
     {
       fitDimensions = FitForFitHeight( requestedSize, sourceSize );
       break;
     }
-  };
+  }
 
   return fitDimensions;
 }
 
 /**
+ * @brief Calculate the number of lines on the X and Y axis that need to be
+ * either added or removed with repect to the specified fitting mode.
+ * (e.g., nearest or linear).
+ * @param[in]     sourceSize      The size of the source image
+ * @param[in]     fittingMode     The fitting mode to use
+ * @param[in/out] requestedSize   The target size that the image will be fitted to.
+ *                                If the source image is smaller than the requested size, the source is not scaled up.
+ *                                So we reduce the target size while keeping aspect by lowering resolution.
+ * @param[out]    scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
+ * @param[out]    columnsToCrop   The number of columns to remove from the image (can be negative to represent X borders required)
+ */
+void CalculateBordersFromFittingMode(  ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop )
+{
+  const unsigned int sourceWidth( sourceSize.GetWidth() );
+  const unsigned int sourceHeight( sourceSize.GetHeight() );
+  const float targetAspect( static_cast< float >( requestedSize.GetWidth() ) / static_cast< float >( requestedSize.GetHeight() ) );
+  int finalWidth = 0;
+  int finalHeight = 0;
+
+  switch( fittingMode )
+  {
+    case FittingMode::FIT_WIDTH:
+    {
+      finalWidth = sourceWidth;
+      finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
+
+      columnsToCrop = 0;
+      scanlinesToCrop = -( finalHeight - sourceHeight );
+      break;
+    }
+
+    case FittingMode::FIT_HEIGHT:
+    {
+      finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
+      finalHeight = sourceHeight;
+
+      columnsToCrop = -( finalWidth - sourceWidth );
+      scanlinesToCrop = 0;
+      break;
+    }
+
+    case FittingMode::SHRINK_TO_FIT:
+    {
+      const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
+      if( sourceAspect > targetAspect )
+      {
+        finalWidth = sourceWidth;
+        finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
+
+        columnsToCrop = 0;
+        scanlinesToCrop = -( finalHeight - sourceHeight );
+      }
+      else
+      {
+        finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
+        finalHeight = sourceHeight;
+
+        columnsToCrop = -( finalWidth - sourceWidth );
+        scanlinesToCrop = 0;
+      }
+      break;
+    }
+
+    case FittingMode::SCALE_TO_FILL:
+    {
+      const float sourceAspect( static_cast< float >( sourceWidth ) / static_cast< float >( sourceHeight ) );
+      if( sourceAspect > targetAspect )
+      {
+        finalWidth = static_cast< float >( sourceHeight ) * targetAspect;
+        finalHeight = sourceHeight;
+
+        columnsToCrop = -( finalWidth - sourceWidth );
+        scanlinesToCrop = 0;
+      }
+      else
+      {
+        finalWidth = sourceWidth;
+        finalHeight = static_cast< float >( sourceWidth ) / targetAspect;
+
+        columnsToCrop = 0;
+        scanlinesToCrop = -( finalHeight - sourceHeight );
+      }
+      break;
+    }
+  }
+
+  requestedSize.SetWidth( finalWidth );
+  requestedSize.SetHeight( finalHeight );
+}
+
+/**
  * @brief Construct a bitmap with format and dimensions requested.
  */
 BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsigned int height )
@@ -345,7 +419,7 @@ BitmapPtr MakeEmptyBitmap( Pixel::Format pixelFormat, unsigned int width, unsign
   DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
 
   // Allocate a pixel buffer to hold the image passed in:
-  Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
+  Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD );
   newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
   return newBitmap;
 }
@@ -365,47 +439,100 @@ BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, u
   return newBitmap;
 }
 
+/**
+ * @brief Work out the desired width and height, accounting for zeros.
+ *
+ * @param[in] bitmapWidth Width of image before processing.
+ * @param[in] bitmapHeight Height of image before processing.
+ * @param[in] requestedWidth Width of area to scale image into. Can be zero.
+ * @param[in] requestedHeight Height of area to scale image into. Can be zero.
+ * @return Dimensions of area to scale image into after special rules are applied.
+ */
+ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
+{
+  unsigned int maxSize = Dali::GetMaxTextureSize();
+
+  // If no dimensions have been requested, default to the source ones:
+  if( requestedWidth == 0 && requestedHeight == 0 )
+  {
+    return ImageDimensions( std::min( bitmapWidth, maxSize ), std::min( bitmapHeight, maxSize ) );
+  }
+
+  // If both dimensions have values requested, use them both:
+  if( requestedWidth != 0 && requestedHeight != 0 )
+  {
+    return ImageDimensions( std::min( requestedWidth, maxSize ), std::min( requestedHeight, maxSize ) );
+  }
+
+  // Only one of the dimensions has been requested. Calculate the other from
+  // the requested one and the source image aspect ratio:
+  if( requestedWidth != 0 )
+  {
+    requestedWidth = std::min( requestedWidth, maxSize );
+    return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
+  }
+
+  requestedHeight = std::min( requestedHeight, maxSize );
+  return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
+}
+
 } // namespace - unnamed
 
+ImageDimensions CalculateDesiredDimensions( ImageDimensions rawDimensions, ImageDimensions requestedDimensions )
+{
+  return CalculateDesiredDimensions( rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight() ) ;
+}
+
 /**
- * @brief Implement ImageAttributes::ScaleTofill scaling mode cropping.
+ * @brief Apply cropping and padding for specified fitting mode.
  *
- * Implement the cropping required for ImageAttributes::ScaleToFill mode,
- * returning a new bitmap with the aspect ratio specified by the scaling mode.
- * This scaling mode selects the central portion of a source image so any spare
- * pixels off one of either the top or bottom edge need to be removed.
+ * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
+ * based on the fitting mode.
  *
- * @note If the input bitmap was not previously downscaled to exactly encompass
- * the desired output size, the resulting bitmap will have the correct aspect
- * ratio but will have larger dimensions than requested. This can be used to
- * fake the scaling mode by relying on the GPU scaling at render time.
- * If the input bitmap was previously maximally downscaled using a
- * repeated box filter, this is a reasonable approach.
+ * This will add vertical or horizontal borders if necessary.
+ * Crop the source image data vertically or horizontally if necessary.
+ * The aspect of the source image is preserved.
+ * If the source image is smaller than the desired size, the algorithm will modify the the newly created
+ *   bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
+ *   GPU scaling to be performed at render time giving the same result with less texture traversal.
  *
- * @return The bitmap passed in if no scaling is needed or possible, else a new,
- * smaller bitmap with the cropping required for the scaling mode applied.
+ * @param[in] bitmap            The source bitmap to perform modifications on.
+ * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
+ * @param[in] fittingMode       The fitting mode to use.
+ *
+ * @return                      A new bitmap with the padding and cropping required for fitting mode applied.
+ *                              If no modification is needed or possible, the passed in bitmap is returned.
  */
-Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
+Integration::BitmapPtr CropAndPadForFittingMode( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode );
 
-BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
-{
-  ///@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.
+/**
+ * @brief Adds horizontal or vertical borders to the source image.
+ *
+ * @param[in] targetPixels     The destination image pointer to draw the borders on.
+ * @param[in] bytesPerPixel    The number of bytes per pixel of the target pixel buffer.
+ * @param[in] targetDimensions The dimensions of the destination image.
+ * @param[in] padDimensions    The columns and scanlines to pad with borders.
+ */
+void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions );
 
+BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode )
+{
   if( bitmap )
   {
     // Calculate the desired box, accounting for a possible zero component:
-    const ImageDimensions desiredDimensions  = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), requestedAttributes.GetWidth(), requestedAttributes.GetHeight() );
+    const ImageDimensions desiredDimensions  = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), dimensions.GetWidth(), dimensions.GetHeight() );
 
     // If a different size than the raw one has been requested, resize the image
     // maximally using a repeated box filter without making it smaller than the
     // requested size in either dimension:
-    bitmap = DownscaleBitmap( *bitmap, desiredDimensions, requestedAttributes.GetScalingMode(), requestedAttributes.GetFilterMode() );
+    bitmap = DownscaleBitmap( *bitmap, desiredDimensions, fittingMode, samplingMode );
 
     // Cut the bitmap according to the desired width and height so that the
-    // resulting bitmap has the same aspect ratio as the desired dimensions:
-    if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
+    // resulting bitmap has the same aspect ratio as the desired dimensions.
+    // Add crop and add borders if necessary depending on fitting mode.
+    if( bitmap && bitmap->GetPackedPixelsProfile() )
     {
-      bitmap = CropForScaleToFill( bitmap, desiredDimensions );
+      bitmap = CropAndPadForFittingMode( bitmap, desiredDimensions, fittingMode );
     }
 
     // Examine the image pixels remaining after cropping and scaling to see if all
@@ -419,69 +546,95 @@ BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, const ImageAttributes& requ
   return bitmap;
 }
 
-BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
+BitmapPtr CropAndPadForFittingMode( BitmapPtr bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode )
 {
-  const unsigned inputWidth = bitmap->GetImageWidth();
-  const unsigned inputHeight = bitmap->GetImageHeight();
-  const unsigned desiredWidth = desiredDimensions.GetWidth();
-  const unsigned desiredHeight = desiredDimensions.GetHeight();
+  const unsigned int inputWidth = bitmap->GetImageWidth();
+  const unsigned int inputHeight = bitmap->GetImageHeight();
 
-  if( desiredWidth < 1U || desiredHeight < 1U )
+  if( desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u )
   {
-    DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
+    DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight() );
   }
-  else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
+  else if( inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight() )
   {
-    const Vector2 desiredDims( desiredWidth, desiredHeight );
-
-    // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
-    // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
-    const float widthsRatio = inputWidth / float(desiredWidth);
-    const Vector2 scaledByWidth = desiredDims * widthsRatio;
-    const float heightsRatio = inputHeight / float(desiredHeight);
-    const Vector2 scaledByHeight = desiredDims * heightsRatio;
-    // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
-    const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
-    const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
-
-    // Work out how many pixels to trim from top and bottom, and left and right:
-    // (We only ever do one dimension)
-    const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - inputHeight) * 0.5f ) : 0;
-    const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - inputWidth) * 0.5f );
-
-    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" );
-
-    // Make a new bitmap with the central part of the loaded one if required:
-    if( scanlinesToTrim > 0 || columnsToTrim > 0 )
-    {
-      const unsigned newWidth = inputWidth - 2 * columnsToTrim;
-      const unsigned newHeight = inputHeight - 2 * scanlinesToTrim;
-      BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
-      Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
-      DALI_ASSERT_DEBUG( packedView );
-      const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
-      packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
+    // Calculate any padding or cropping that needs to be done based on the fitting mode.
+    // Note: If the desired size is larger than the original image, the desired size will be
+    // reduced while maintaining the aspect, in order to save unnecessary memory usage.
+    int scanlinesToCrop = 0;
+    int columnsToCrop = 0;
 
-      const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
+    CalculateBordersFromFittingMode( ImageDimensions( inputWidth, inputHeight ), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop );
 
-      const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
-      PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
-      DALI_ASSERT_DEBUG( srcPixels && destPixels );
+    unsigned int desiredWidth( desiredDimensions.GetWidth() );
+    unsigned int desiredHeight( desiredDimensions.GetHeight() );
 
-      // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
-      if( trimTopAndBottom )
+    // Action the changes by making a new bitmap with the central part of the loaded one if required.
+    if( scanlinesToCrop != 0 || columnsToCrop != 0 )
+    {
+      // Split the adding and removing of scanlines and columns into separate variables,
+      // so we can use one piece of generic code to action the changes.
+      unsigned int scanlinesToPad = 0;
+      unsigned int columnsToPad = 0;
+      if( scanlinesToCrop < 0 )
       {
-        memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
+        scanlinesToPad = -scanlinesToCrop;
+        scanlinesToCrop = 0;
+      }
+      if( columnsToCrop < 0 )
+      {
+        columnsToPad = -columnsToCrop;
+        columnsToCrop = 0;
+      }
+
+      // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
+      if( ( desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE ) || ( desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE ) ||
+          ( columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE ) || ( scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE ) )
+      {
+        DALI_LOG_WARNING( "Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight );
+        return bitmap;
+      }
+
+      // Create a new bitmap with the desired size.
+      BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::OWNED_DISCARD );
+      Integration::Bitmap::PackedPixelsProfile *packedView = croppedBitmap->GetPackedPixelsProfile();
+      DALI_ASSERT_DEBUG( packedView );
+      const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
+      packedView->ReserveBuffer( pixelFormat, desiredWidth, desiredHeight, desiredWidth, desiredHeight );
+
+      // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
+      // The cropping is added to the source pointer, and the padding is added to the destination.
+      const unsigned int bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
+      const PixelBuffer * const sourcePixels = bitmap->GetBuffer() + ( ( ( ( scanlinesToCrop / 2 ) * inputWidth ) + ( columnsToCrop / 2 ) ) * bytesPerPixel );
+      PixelBuffer * const targetPixels = croppedBitmap->GetBuffer();
+      PixelBuffer * const targetPixelsActive = targetPixels + ( ( ( ( scanlinesToPad / 2 ) * desiredWidth ) + ( columnsToPad / 2 ) ) * bytesPerPixel );
+      DALI_ASSERT_DEBUG( sourcePixels && targetPixels );
+
+      // Copy the image data to the new bitmap.
+      // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
+      unsigned int outputSpan( desiredWidth * bytesPerPixel );
+      if( columnsToCrop == 0 && columnsToPad == 0 )
+      {
+        memcpy( targetPixelsActive, sourcePixels, ( desiredHeight - scanlinesToPad ) * outputSpan );
       }
       else
       {
-        for( unsigned y = 0; y < newHeight; ++y )
+        // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
+        // Precalculate any constants to optimize the inner loop.
+        const unsigned int inputSpan( inputWidth * bytesPerPixel );
+        const unsigned int copySpan( ( desiredWidth - columnsToPad ) * bytesPerPixel );
+        const unsigned int scanlinesToCopy( desiredHeight - scanlinesToPad );
+
+        for( unsigned int y = 0; y < scanlinesToCopy; ++y )
         {
-          memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
+          memcpy( &targetPixelsActive[ y * outputSpan ], &sourcePixels[ y * inputSpan ], copySpan );
         }
       }
 
-      // Overwrite the loaded bitmap with the cropped version:
+      // Add vertical or horizontal borders to the final image (if required).
+      desiredDimensions.SetWidth( desiredWidth );
+      desiredDimensions.SetHeight( desiredHeight );
+      AddBorders( croppedBitmap->GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions( columnsToPad, scanlinesToPad ) );
+      // Overwrite the loaded bitmap with the cropped version
       bitmap = croppedBitmap;
     }
   }
@@ -489,10 +642,58 @@ BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimension
   return bitmap;
 }
 
+void AddBorders( PixelBuffer *targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions )
+{
+  // Assign ints for faster access.
+  unsigned int desiredWidth( targetDimensions.GetWidth() );
+  unsigned int desiredHeight( targetDimensions.GetHeight() );
+  unsigned int columnsToPad( padDimensions.GetWidth() );
+  unsigned int scanlinesToPad( padDimensions.GetHeight() );
+  unsigned int outputSpan( desiredWidth * bytesPerPixel );
+
+  // Add letterboxing (symmetrical borders) if needed.
+  if( scanlinesToPad > 0 )
+  {
+    // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
+    memset( targetPixels, BORDER_FILL_VALUE, ( scanlinesToPad / 2 ) * outputSpan );
+
+    // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
+    // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
+    unsigned int bottomBorderHeight = scanlinesToPad - ( scanlinesToPad / 2 );
+
+    // Bottom border.
+    memset( &targetPixels[ ( desiredHeight - bottomBorderHeight ) * outputSpan ], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan );
+  }
+  else if( columnsToPad > 0 )
+  {
+    // Add a left and right border.
+    // Left:
+    // Pre-calculate span size outside of loop.
+    unsigned int leftBorderSpanWidth( ( columnsToPad / 2 ) * bytesPerPixel );
+    for( unsigned int y = 0; y < desiredHeight; ++y )
+    {
+      memset( &targetPixels[ y * outputSpan ], BORDER_FILL_VALUE, leftBorderSpanWidth );
+    }
+
+    // Right:
+    // Pre-calculate the initial x offset as it is always the same for a small optimization.
+    // We subtract columnsToPad/2 from columnsToPad so that we have the correct
+    // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
+    unsigned int rightBorderWidth = columnsToPad - ( columnsToPad / 2 );
+    PixelBuffer * const destPixelsRightBorder( targetPixels + ( ( desiredWidth - rightBorderWidth ) * bytesPerPixel ) );
+    unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
+
+    for( unsigned int y = 0; y < desiredHeight; ++y )
+    {
+      memset( &destPixelsRightBorder[ y * outputSpan ], BORDER_FILL_VALUE, rightBorderSpanWidth );
+    }
+  }
+}
+
 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
                                         ImageDimensions desired,
-                                        ImageAttributes::ScalingMode scalingMode,
-                                        ImageAttributes::FilterMode filterMode )
+                                        FittingMode::Type fittingMode,
+                                        SamplingMode::Type samplingMode )
 {
   // Source dimensions as loaded from resources (e.g. filesystem):
   const unsigned int bitmapWidth  = bitmap.GetImageWidth();
@@ -512,10 +713,10 @@ Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
 
     // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
     unsigned int shrunkWidth = -1, shrunkHeight = -1;
-    DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, scalingMode, filterMode, shrunkWidth, shrunkHeight );
+    DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight );
 
     // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
-    const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), scalingMode );
+    const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), fittingMode );
     const unsigned int filteredWidth = filteredDimensions.GetWidth();
     const unsigned int filteredHeight = filteredDimensions.GetHeight();
 
@@ -523,13 +724,13 @@ Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
     bool filtered = false;
     if( filteredWidth < shrunkWidth || filteredHeight < shrunkHeight )
     {
-      if( filterMode == ImageAttributes::Linear || filterMode == ImageAttributes::BoxThenLinear ||
-          filterMode == ImageAttributes::Nearest || filterMode == ImageAttributes::BoxThenNearest )
+      if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
+          samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST )
       {
         outputBitmap = MakeEmptyBitmap( pixelFormat, filteredWidth, filteredHeight );
         if( outputBitmap )
         {
-          if( filterMode == ImageAttributes::Linear || filterMode == ImageAttributes::BoxThenLinear )
+          if( samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR )
           {
             LinearSample( bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap->GetBuffer(), filteredDimensions );
           }
@@ -848,20 +1049,20 @@ void DownscaleInPlacePow2( unsigned char * const pixels,
                            unsigned int inputHeight,
                            unsigned int desiredWidth,
                            unsigned int desiredHeight,
-                           ImageAttributes::ScalingMode scalingMode,
-                           ImageAttributes::FilterMode filterMode,
+                           FittingMode::Type fittingMode,
+                           SamplingMode::Type samplingMode,
                            unsigned& outWidth,
                            unsigned& outHeight )
 {
   outWidth = inputWidth;
   outHeight = inputHeight;
   // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
-  if( filterMode == ImageAttributes::Box || filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear )
+  if( samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR )
   {
     // Check the pixel format is one that is supported:
     if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
     {
-      const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( scalingMode );
+      const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( fittingMode );
 
       if( pixelFormat == Pixel::RGBA8888 )
       {
@@ -982,8 +1183,8 @@ inline void PointSampleAddressablePixels( const uint8_t * inPixels,
   DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
       outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
       "The input and output buffers must not overlap for an upscaling.");
-  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, ...)." );
-  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, ...)." );
+  DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( inPixels )  % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+  DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( outPixels ) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
 
   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
   {
@@ -1225,8 +1426,8 @@ inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
                      "Input and output buffers cannot overlap.");
   if( DEBUG_ASSERT_ALIGNMENT )
   {
-    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, ...)." );
-    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, ...)." );
+    DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( inPixels )  % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+    DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
   }
 
   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
@@ -1322,6 +1523,200 @@ void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
   LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
 }
 
+void LanczosSample( const unsigned char * __restrict__ inPixels,
+                    ImageDimensions inputDimensions,
+                    unsigned char * __restrict__ outPixels,
+                    ImageDimensions desiredDimensions,
+                    int numChannels, bool hasAlpha )
+{
+  // Got from the test.cpp of the ImageResampler lib.
+  const float ONE_DIV_255 = 1.0f / 255.0f;
+  const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
+  const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
+  const int ALPHA_CHANNEL = hasAlpha ? (numChannels-1) : 0;
+
+  static bool loadColorSpaces = true;
+  static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
+  static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
+
+  if( loadColorSpaces ) // Only create the color space conversions on the first execution
+  {
+    loadColorSpaces = false;
+
+    for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i )
+    {
+      srgbToLinear[i] = pow( static_cast<float>( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA );
+    }
+
+    const float invLinearToSrgbTableSize = 1.0f / static_cast<float>( LINEAR_TO_SRGB_TABLE_SIZE );
+    const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
+
+    for( int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i )
+    {
+      int k = static_cast<int>( 255.0f * pow( static_cast<float>( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f );
+      if( k < 0 )
+      {
+        k = 0;
+      }
+      else if( k > MAX_UNSIGNED_CHAR )
+      {
+        k = MAX_UNSIGNED_CHAR;
+      }
+      linearToSrgb[i] = static_cast<unsigned char>( k );
+    }
+  }
+
+  Resampler* resamplers[numChannels];
+  Vector<float> samples[numChannels];
+
+  const int srcWidth = inputDimensions.GetWidth();
+  const int srcHeight = inputDimensions.GetHeight();
+  const int dstWidth = desiredDimensions.GetWidth();
+  const int dstHeight = desiredDimensions.GetHeight();
+
+  // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
+  // used for the other components (a memory and slight cache efficiency optimization).
+  resamplers[0] = new Resampler( srcWidth,
+                                 srcHeight,
+                                 dstWidth,
+                                 dstHeight,
+                                 Resampler::BOUNDARY_CLAMP,
+                                 0.0f,           // sample_low,
+                                 1.0f,           // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
+                                 FILTER_TYPE,    // The type of filter. Currently Lanczos.
+                                 NULL,           // Pclist_x,
+                                 NULL,           // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
+                                 FILTER_SCALE,   // src_x_ofs,
+                                 FILTER_SCALE ); // src_y_ofs. Offset input image by specified amount (fractional values okay).
+  samples[0].Resize( srcWidth );
+  for( int i = 1; i < numChannels; ++i )
+  {
+    resamplers[i] = new Resampler( srcWidth,
+                                   srcHeight,
+                                   dstWidth,
+                                   dstHeight,
+                                   Resampler::BOUNDARY_CLAMP,
+                                   0.0f,
+                                   1.0f,
+                                   FILTER_TYPE,
+                                   resamplers[0]->get_clist_x(),
+                                   resamplers[0]->get_clist_y(),
+                                   FILTER_SCALE,
+                                   FILTER_SCALE );
+    samples[i].Resize( srcWidth );
+  }
+
+  const int srcPitch = srcWidth * numChannels;
+  const int dstPitch = dstWidth * numChannels;
+  int dstY = 0;
+
+  for( int srcY = 0; srcY < srcHeight; ++srcY )
+  {
+    const unsigned char* pSrc = &inPixels[srcY * srcPitch];
+
+    for( int x = 0; x < srcWidth; ++x )
+    {
+      for( int c = 0; c < numChannels; ++c )
+      {
+        if( c == ALPHA_CHANNEL && hasAlpha )
+        {
+          samples[c][x] = *pSrc++ * ONE_DIV_255;
+        }
+        else
+        {
+          samples[c][x] = srgbToLinear[*pSrc++];
+        }
+      }
+    }
+
+    for( int c = 0; c < numChannels; ++c )
+    {
+      if( !resamplers[c]->put_line( &samples[c][0] ) )
+      {
+        DALI_ASSERT_DEBUG( !"Out of memory" );
+      }
+    }
+
+    for(;;)
+    {
+      int compIndex;
+      for( compIndex = 0; compIndex < numChannels; ++compIndex )
+      {
+        const float* pOutputSamples = resamplers[compIndex]->get_line();
+        if( !pOutputSamples )
+        {
+          break;
+        }
+
+        const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL && hasAlpha );
+        DALI_ASSERT_DEBUG( dstY < dstHeight );
+        unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
+
+        for( int x = 0; x < dstWidth; ++x )
+        {
+          if( isAlphaChannel )
+          {
+            int c = static_cast<int>( 255.0f * pOutputSamples[x] + 0.5f );
+            if( c < 0 )
+            {
+              c = 0;
+            }
+            else if( c > MAX_UNSIGNED_CHAR )
+            {
+              c = MAX_UNSIGNED_CHAR;
+            }
+            *pDst = static_cast<unsigned char>( c );
+          }
+          else
+          {
+            int j = static_cast<int>( LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f );
+            if( j < 0 )
+            {
+              j = 0;
+            }
+            else if( j >= LINEAR_TO_SRGB_TABLE_SIZE )
+            {
+              j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
+            }
+            *pDst = linearToSrgb[j];
+          }
+
+          pDst += numChannels;
+        }
+      }
+      if( compIndex < numChannels )
+      {
+        break;
+      }
+
+      ++dstY;
+    }
+  }
+
+  // Delete the resamplers.
+  for( int i = 0; i < numChannels; ++i )
+  {
+    delete resamplers[i];
+  }
+}
+
+void LanczosSample4BPP( const unsigned char * __restrict__ inPixels,
+                        ImageDimensions inputDimensions,
+                        unsigned char * __restrict__ outPixels,
+                        ImageDimensions desiredDimensions )
+{
+  LanczosSample( inPixels, inputDimensions, outPixels, desiredDimensions, 4, true );
+}
+
+void LanczosSample1BPP( const unsigned char * __restrict__ inPixels,
+                        ImageDimensions inputDimensions,
+                        unsigned char * __restrict__ outPixels,
+                        ImageDimensions desiredDimensions )
+{
+  // For L8 images
+  LanczosSample( inPixels, inputDimensions, outPixels, desiredDimensions, 1, false );
+}
+
 // Dispatch to a format-appropriate linear sampling function:
 void LinearSample( const unsigned char * __restrict__ inPixels,
                    ImageDimensions inDimensions,