2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #include "image-operations.h"
23 #include <dali/integration-api/debug.h>
36 using Integration::Bitmap;
37 using Integration::BitmapPtr;
38 typedef unsigned char PixelBuffer;
40 #if defined(DEBUG_ENABLED)
42 * Disable logging of image operations or make it verbose from the commandline
43 * as follows (e.g., for dali demo app):
45 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
46 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
49 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS" );
52 /** @return The greatest even number less than or equal to the argument. */
53 inline unsigned int EvenDown( const unsigned int a )
55 const unsigned int evened = a & ~1u;
60 * @brief Log bad parameters.
62 void ValidateScalingParameters( const unsigned int inputWidth,
63 const unsigned int inputHeight,
64 const unsigned int desiredWidth,
65 const unsigned int desiredHeight )
67 if( desiredWidth > inputWidth || desiredHeight > inputHeight )
69 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight );
72 if( desiredWidth == 0u || desiredHeight == 0u )
74 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless." );
77 if( inputWidth == 0u || inputHeight == 0u )
79 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled" );
84 * @brief Do debug assertions common to all scanline halving functions.
85 * @note Inline and in anon namespace so should boil away in release builds.
87 inline void DebugAssertScanlineParameters( const uint8_t * const pixels, const unsigned int width )
89 DALI_ASSERT_DEBUG( pixels && "Null pointer." );
90 DALI_ASSERT_DEBUG( width > 1u && "Can't average fewer than two pixels." );
91 DALI_ASSERT_DEBUG( width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?" );
95 * @brief Assertions on params to functions averaging pairs of scanlines.
96 * @note Inline as intended to boil away in release.
98 inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
99 const uint8_t * const scanline2,
100 uint8_t* const outputScanline,
101 const size_t widthInComponents )
103 DALI_ASSERT_DEBUG( scanline1 && "Null pointer." );
104 DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
105 DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
106 DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
107 DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
111 * @brief Work out the desired width and height according to the rules documented for the ImageAttributes class.
113 * @param[in] bitmapWidth Width of image before processing.
114 * @param[in] bitmapHeight Height of image before processing.
115 * @param[in] requestedWidth Width of area to scale image into. Can be zero.
116 * @param[in] requestedHeight Height of area to scale image into. Can be zero.
117 * @return Dimensions of area to scale image into after special rules are applied.
118 * @see ImageAttributes
120 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
122 // If no dimensions have been requested, default to the source ones:
123 if( requestedWidth == 0 && requestedHeight == 0 )
125 return ImageDimensions( bitmapWidth, bitmapHeight );
128 // If both dimensions have values requested, use them both:
129 if( requestedWidth != 0 && requestedHeight != 0 )
131 return ImageDimensions( requestedWidth, requestedHeight );
134 // If only one of the dimensions has been requested, calculate the other from
135 // the requested one and the source image aspect ratio:
137 if( requestedWidth != 0 )
139 return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
141 return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
145 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
147 BoxDimensionTest DimensionTestForScalingMode( ImageAttributes::ScalingMode scalingMode )
149 BoxDimensionTest dimensionTest;
150 dimensionTest = BoxDimensionTestEither;
152 switch( scalingMode )
154 // Shrink to fit attempts to make one or zero dimensions smaller than the
155 // desired dimensions and one or two dimensions exactly the same as the desired
156 // ones, so as long as one dimension is larger than the desired size, box
157 // filtering can continue even if the second dimension is smaller than the
158 // desired dimensions:
159 case ImageAttributes::ShrinkToFit:
161 dimensionTest = BoxDimensionTestEither;
164 // Scale to fill mode keeps both dimensions at least as large as desired:
165 case ImageAttributes::ScaleToFill:
167 dimensionTest = BoxDimensionTestBoth;
170 // Y dimension is irrelevant when downscaling in FitWidth mode:
171 case ImageAttributes::FitWidth:
173 dimensionTest = BoxDimensionTestX;
176 // X Dimension is ignored by definition in FitHeight mode:
177 case ImageAttributes::FitHeight:
179 dimensionTest = BoxDimensionTestY;
184 return dimensionTest;
188 * @brief Work out the dimensions for a uniform scaling of the input to map it
189 * into the target while effecting ShinkToFit scaling mode.
191 ImageDimensions FitForShrinkToFit( ImageDimensions target, ImageDimensions source )
193 DALI_ASSERT_DEBUG( true && " " );
194 // Scale the input by the least extreme of the two dimensions:
195 const float widthScale = target.GetX() / float(source.GetX());
196 const float heightScale = target.GetY() / float(source.GetY());
197 const float scale = widthScale < heightScale ? widthScale : heightScale;
199 // Do no scaling at all if the result would increase area:
205 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
209 * @brief Work out the dimensions for a uniform scaling of the input to map it
210 * into the target while effecting ScaleToFill scaling mode.
211 * @note The output dimensions will need either top and bottom or left and right
212 * to be cropped away unless the source was pre-cropped to match the destination
215 ImageDimensions FitForScaleToFill( ImageDimensions target, ImageDimensions source )
217 DALI_ASSERT_DEBUG( source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in" );
218 // Scale the input by the least extreme of the two dimensions:
219 const float widthScale = target.GetX() / float(source.GetX());
220 const float heightScale = target.GetY() / float(source.GetY());
221 const float scale = widthScale > heightScale ? widthScale : heightScale;
223 // Do no scaling at all if the result would increase area:
229 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
233 * @brief Work out the dimensions for a uniform scaling of the input to map it
234 * into the target while effecting FitWidth scaling mode.
236 ImageDimensions FitForFitWidth( ImageDimensions target, ImageDimensions source )
238 DALI_ASSERT_DEBUG( source.GetX() > 0 && "Cant fit a zero-dimension rectangle." );
239 const float scale = target.GetX() / float(source.GetX());
241 // Do no scaling at all if the result would increase area:
246 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
250 * @brief Work out the dimensions for a uniform scaling of the input to map it
251 * into the target while effecting FitHeight scaling mode.
253 ImageDimensions FitForFitHeight( ImageDimensions target, ImageDimensions source )
255 DALI_ASSERT_DEBUG( source.GetY() > 0 && "Cant fit a zero-dimension rectangle." );
256 const float scale = target.GetY() / float(source.GetY());
258 // Do no scaling at all if the result would increase area:
264 return ImageDimensions( source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f );
268 * @brief Generate the rectangle to use as the target of a pixel sampling pass
269 * (e.g., nearest or linear).
271 ImageDimensions FitToScalingMode( ImageDimensions requestedSize, ImageDimensions sourceSize, ImageAttributes::ScalingMode scalingMode )
273 ImageDimensions fitDimensions;
274 switch( scalingMode )
276 case ImageAttributes::ShrinkToFit:
278 fitDimensions = FitForShrinkToFit( requestedSize, sourceSize );
281 case ImageAttributes::ScaleToFill:
283 fitDimensions = FitForScaleToFill( requestedSize, sourceSize );
286 case ImageAttributes::FitWidth:
288 fitDimensions = FitForFitWidth( requestedSize, sourceSize );
291 case ImageAttributes::FitHeight:
293 fitDimensions = FitForFitHeight( requestedSize, sourceSize );
298 return fitDimensions;
302 * @brief Construct a bitmap object from a copy of the pixel array passed in.
304 BitmapPtr MakeBitmap(const uint8_t * const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height )
306 DALI_ASSERT_DEBUG( pixels && "Null bitmap buffer to copy." );
307 DALI_ASSERT_DEBUG( Pixel::GetBytesPerPixel(pixelFormat) && "Compressed formats not supported." );
309 // Allocate a pixel buffer to hold the image passed in:
310 Integration::BitmapPtr newBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
311 newBitmap->GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, width, height, width, height );
313 // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
314 memcpy( newBitmap->GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel( pixelFormat ) );
318 } // namespace - unnamed
321 * @brief Implement ImageAttributes::ScaleTofill scaling mode cropping.
323 * Implement the cropping required for ImageAttributes::ScaleToFill mode,
324 * returning a new bitmap with the aspect ratio specified by the scaling mode.
325 * This scaling mode selects the central portion of a source image so any spare
326 * pixels off one of either the top or bottom edge need to be removed.
328 * @note If the input bitmap was not previously downscaled to exactly encompass
329 * the desired output size, the resulting bitmap will have the correct aspect
330 * ratio but will have larger dimensions than requested. This can be used to
331 * fake the scaling mode by relying on the GPU scaling at render time.
332 * If the input bitmap was previously maximally downscaled using a
333 * repeated box filter, this is a reasonable approach.
335 * @return The bitmap passed in if no scaling is needed or possible, else a new,
336 * smaller bitmap with the cropping required for the scaling mode applied.
338 Integration::BitmapPtr CropForScaleToFill( Integration::BitmapPtr bitmap, ImageDimensions desiredDimensions );
340 BitmapPtr ApplyAttributesToBitmap( BitmapPtr bitmap, const ImageAttributes& requestedAttributes )
342 ///@ToDo: Optimisation - If Scaling Mode is ScaletoFill, either do cropping here at the front of the pipe, or equivalently modify all scaling functions to take a source rectangle and have the first one to be applied, pull in a subset of source pixels to crop on the fly. That would make every scaling slightly slower but would save the memcpy()s at the end for ScaleToFill.
346 // Calculate the desired box, accounting for a possible zero component:
347 const ImageDimensions desiredDimensions = CalculateDesiredDimensions( bitmap->GetImageWidth(), bitmap->GetImageHeight(), requestedAttributes.GetWidth(), requestedAttributes.GetHeight() );
349 // If a different size than the raw one has been requested, resize the image
350 // maximally using a repeated box filter without making it smaller than the
351 // requested size in either dimension:
352 bitmap = DownscaleBitmap( *bitmap, desiredDimensions, requestedAttributes.GetScalingMode(), requestedAttributes.GetFilterMode() );
354 // Cut the bitmap according to the desired width and height so that the
355 // resulting bitmap has the same aspect ratio as the desired dimensions:
356 if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
358 bitmap = CropForScaleToFill( bitmap, desiredDimensions );
361 // Examine the image pixels remaining after cropping and scaling to see if all
362 // are opaque, allowing faster rendering, or some have non-1.0 alpha:
363 if( bitmap && bitmap->GetPackedPixelsProfile() && Pixel::HasAlpha( bitmap->GetPixelFormat() ) )
365 bitmap->GetPackedPixelsProfile()->TestForTransparency();
372 BitmapPtr CropForScaleToFill( BitmapPtr bitmap, ImageDimensions desiredDimensions )
374 const unsigned inputWidth = bitmap->GetImageWidth();
375 const unsigned inputHeight = bitmap->GetImageHeight();
376 const unsigned desiredWidth = desiredDimensions.GetWidth();
377 const unsigned desiredHeight = desiredDimensions.GetHeight();
379 if( desiredWidth < 1U || desiredHeight < 1U )
381 DALI_LOG_WARNING( "Image scaling aborted as desired dimensions too small (%u, %u)\n.", desiredWidth, desiredHeight );
383 else if( inputWidth != desiredWidth || inputHeight != desiredHeight )
385 const Vector2 desiredDims( desiredWidth, desiredHeight );
387 // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
388 // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
389 const float widthsRatio = inputWidth / float(desiredWidth);
390 const Vector2 scaledByWidth = desiredDims * widthsRatio;
391 const float heightsRatio = inputHeight / float(desiredHeight);
392 const Vector2 scaledByHeight = desiredDims * heightsRatio;
393 // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
394 const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
395 const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
397 // Work out how many pixels to trim from top and bottom, and left and right:
398 // (We only ever do one dimension)
399 const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - inputHeight) * 0.5f ) : 0;
400 const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - inputWidth) * 0.5f );
402 DALI_LOG_INFO( gImageOpsLogFilter, Debug::Concise, "Bitmap, desired(%f, %f), loaded(%u,%u), cut_target(%f, %f), trimmed(%u, %u), vertical = %s.\n", desiredDims.x, desiredDims.y, inputWidth, inputHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" );
404 // Make a new bitmap with the central part of the loaded one if required:
405 if( scanlinesToTrim > 0 || columnsToTrim > 0 )
407 const unsigned newWidth = inputWidth - 2 * columnsToTrim;
408 const unsigned newHeight = inputHeight - 2 * scanlinesToTrim;
409 BitmapPtr croppedBitmap = Integration::Bitmap::New( Integration::Bitmap::BITMAP_2D_PACKED_PIXELS, ResourcePolicy::DISCARD );
410 Integration::Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
411 DALI_ASSERT_DEBUG( packedView );
412 const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
413 packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
415 const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
417 const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * inputWidth * bytesPerPixel;
418 PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
419 DALI_ASSERT_DEBUG( srcPixels && destPixels );
421 // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
422 if( trimTopAndBottom )
424 memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
428 for( unsigned y = 0; y < newHeight; ++y )
430 memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * inputWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
434 // Overwrite the loaded bitmap with the cropped version:
435 bitmap = croppedBitmap;
442 Integration::BitmapPtr DownscaleBitmap( Integration::Bitmap& bitmap,
443 ImageDimensions desired,
444 ImageAttributes::ScalingMode scalingMode,
445 ImageAttributes::FilterMode filterMode )
447 // Source dimensions as loaded from resources (e.g. filesystem):
448 const unsigned int bitmapWidth = bitmap.GetImageWidth();
449 const unsigned int bitmapHeight = bitmap.GetImageHeight();
450 // Desired dimensions (the rectangle to fit the source image to):
451 const unsigned int desiredWidth = desired.GetWidth();
452 const unsigned int desiredHeight = desired.GetHeight();
454 BitmapPtr outputBitmap( &bitmap );
456 // If a different size than the raw one has been requested, resize the image:
457 if( bitmap.GetPackedPixelsProfile() &&
458 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
459 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)) )
461 const Pixel::Format pixelFormat = bitmap.GetPixelFormat();
463 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
464 unsigned int shrunkWidth = -1, shrunkHeight = -1;
465 DownscaleInPlacePow2( bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, scalingMode, filterMode, shrunkWidth, shrunkHeight );
467 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
468 const ImageDimensions filteredDimensions = FitToScalingMode( ImageDimensions( desiredWidth, desiredHeight ), ImageDimensions( shrunkWidth, shrunkHeight ), scalingMode );
469 const unsigned int filteredWidth = filteredDimensions.GetWidth();
470 const unsigned int filteredHeight = filteredDimensions.GetHeight();
472 // Run a filter to scale down the bitmap if it needs it:
473 if( (filteredWidth < shrunkWidth || filteredHeight < shrunkHeight) &&
474 (filterMode == ImageAttributes::Nearest || filterMode == ImageAttributes::Linear ||
475 filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear) )
477 ///@note If a linear filter is requested, we do our best with a point filter for now.
478 PointSample( bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, bitmap.GetBuffer(), filteredWidth, filteredHeight );
480 outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, filteredWidth, filteredHeight );
482 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
483 else if( shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight )
485 outputBitmap = MakeBitmap( bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight );
495 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
496 * @param test Which combination of the two dimensions matter for terminating the filtering.
497 * @param scaledWidth The width of the current downscaled image.
498 * @param scaledHeight The height of the current downscaled image.
499 * @param desiredWidth The target width for the downscaling.
500 * @param desiredHeight The target height for the downscaling.
502 bool ContinueScaling( BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight )
504 bool keepScaling = false;
505 const unsigned int nextWidth = scaledWidth >> 1u;
506 const unsigned int nextHeight = scaledHeight >> 1u;
508 if( nextWidth >= 1u && nextHeight >= 1u )
512 case BoxDimensionTestEither:
514 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
517 case BoxDimensionTestBoth:
519 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
522 case BoxDimensionTestX:
524 keepScaling = nextWidth >= desiredWidth;
527 case BoxDimensionTestY:
529 keepScaling = nextHeight >= desiredHeight;
539 * @brief A shared implementation of the overall iterative box filter
540 * downscaling algorithm.
542 * Specialise this for particular pixel formats by supplying the number of bytes
543 * per pixel and two functions: one for averaging pairs of neighbouring pixels
544 * on a single scanline, and a second for averaging pixels at corresponding
545 * positions on different scanlines.
549 void (*HalveScanlineInPlace)( unsigned char * const pixels, const unsigned int width ),
550 void (*AverageScanlines) ( const unsigned char * const scanline1, const unsigned char * const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width )
552 void DownscaleInPlacePow2Generic( unsigned char * const pixels,
553 const unsigned int inputWidth,
554 const unsigned int inputHeight,
555 const unsigned int desiredWidth,
556 const unsigned int desiredHeight,
557 BoxDimensionTest dimensionTest,
559 unsigned& outHeight )
565 ValidateScalingParameters( inputWidth, inputHeight, desiredWidth, desiredHeight );
567 // Scale the image until it would be smaller than desired, stopping if the
568 // resulting height or width would be less than 1:
569 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
570 while( ContinueScaling( dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight ) )
572 const unsigned int lastWidth = scaledWidth;
576 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight );
578 const unsigned int lastScanlinePair = scaledHeight - 1;
580 // Scale pairs of scanlines until any spare one at the end is dropped:
581 for( unsigned int y = 0; y <= lastScanlinePair; ++y )
583 // Scale two scanlines horizontally:
584 HalveScanlineInPlace( &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth );
585 HalveScanlineInPlace( &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth );
587 // Scale vertical pairs of pixels while the last two scanlines are still warm in
589 // Note, better access patterns for cache-coherence are possible for very large
590 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
591 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
593 &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
594 &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
595 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
600 ///@note: we could finish off with one of two mutually exclusive passes, one squashing horizontally as far as possible, and the other vertically, if we knew a following cpu point or bilinear filter would restore the desired aspect ratio.
601 outWidth = scaledWidth;
602 outHeight = scaledHeight;
607 void HalveScanlineInPlaceRGB888( unsigned char * const pixels, const unsigned int width )
609 DebugAssertScanlineParameters( pixels, width );
611 const unsigned int lastPair = EvenDown( width - 2 );
613 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
615 // Load all the byte pixel components we need:
616 const unsigned int c11 = pixels[pixel * 3];
617 const unsigned int c12 = pixels[pixel * 3 + 1];
618 const unsigned int c13 = pixels[pixel * 3 + 2];
619 const unsigned int c21 = pixels[pixel * 3 + 3];
620 const unsigned int c22 = pixels[pixel * 3 + 4];
621 const unsigned int c23 = pixels[pixel * 3 + 5];
623 // Save the averaged byte pixel components:
624 pixels[outPixel * 3] = AverageComponent( c11, c21 );
625 pixels[outPixel * 3 + 1] = AverageComponent( c12, c22 );
626 pixels[outPixel * 3 + 2] = AverageComponent( c13, c23 );
630 void HalveScanlineInPlaceRGBA8888( unsigned char * const pixels, const unsigned int width )
632 DebugAssertScanlineParameters( pixels, width );
633 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
635 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
637 const unsigned int lastPair = EvenDown( width - 2 );
639 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
641 const uint32_t averaged = AveragePixelRGBA8888( alignedPixels[pixel], alignedPixels[pixel + 1] );
642 alignedPixels[outPixel] = averaged;
646 void HalveScanlineInPlaceRGB565( unsigned char * pixels, unsigned int width )
648 DebugAssertScanlineParameters( pixels, width );
649 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
651 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
653 const unsigned int lastPair = EvenDown( width - 2 );
655 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
657 const uint32_t averaged = AveragePixelRGB565( alignedPixels[pixel], alignedPixels[pixel + 1] );
658 alignedPixels[outPixel] = averaged;
662 void HalveScanlineInPlace2Bytes( unsigned char * const pixels, const unsigned int width )
664 DebugAssertScanlineParameters( pixels, width );
666 const unsigned int lastPair = EvenDown( width - 2 );
668 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
670 // Load all the byte pixel components we need:
671 const unsigned int c11 = pixels[pixel * 2];
672 const unsigned int c12 = pixels[pixel * 2 + 1];
673 const unsigned int c21 = pixels[pixel * 2 + 2];
674 const unsigned int c22 = pixels[pixel * 2 + 3];
676 // Save the averaged byte pixel components:
677 pixels[outPixel * 2] = AverageComponent( c11, c21 );
678 pixels[outPixel * 2 + 1] = AverageComponent( c12, c22 );
682 void HalveScanlineInPlace1Byte( unsigned char * const pixels, const unsigned int width )
684 DebugAssertScanlineParameters( pixels, width );
686 const unsigned int lastPair = EvenDown( width - 2 );
688 for( unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel )
690 // Load all the byte pixel components we need:
691 const unsigned int c1 = pixels[pixel];
692 const unsigned int c2 = pixels[pixel + 1];
694 // Save the averaged byte pixel component:
695 pixels[outPixel] = AverageComponent( c1, c2 );
700 * @ToDo: Optimise for ARM using a 4 bytes at a time loop wrapped around the single ARMV6 instruction: UHADD8 R4, R0, R5. Note, this is not neon. It runs in the normal integer pipeline so there is no downside like a stall moving between integer and copro, or extra power for clocking-up the idle copro.
701 * if (widthInComponents >= 7) { word32* aligned1 = scanline1 + 3 & 3; word32* aligned1_end = scanline1 + widthInPixels & 3; while(aligned1 < aligned1_end) { UHADD8 *aligned1++, *aligned2++, *alignedoutput++ } .. + 0 to 3 spare pixels at each end.
703 void AverageScanlines1( const unsigned char * const scanline1,
704 const unsigned char * const __restrict__ scanline2,
705 unsigned char* const outputScanline,
706 const unsigned int width )
708 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width );
710 for( unsigned int component = 0; component < width; ++component )
712 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
716 void AverageScanlines2( const unsigned char * const scanline1,
717 const unsigned char * const __restrict__ scanline2,
718 unsigned char* const outputScanline,
719 const unsigned int width )
721 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
723 for( unsigned int component = 0; component < width * 2; ++component )
725 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
729 void AverageScanlines3( const unsigned char * const scanline1,
730 const unsigned char * const __restrict__ scanline2,
731 unsigned char* const outputScanline,
732 const unsigned int width )
734 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 3 );
736 for( unsigned int component = 0; component < width * 3; ++component )
738 outputScanline[component] = AverageComponent( scanline1[component], scanline2[component] );
742 void AverageScanlinesRGBA8888( const unsigned char * const scanline1,
743 const unsigned char * const __restrict__ scanline2,
744 unsigned char * const outputScanline,
745 const unsigned int width )
747 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 4 );
748 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
749 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
750 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
752 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
753 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
754 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
756 for( unsigned int pixel = 0; pixel < width; ++pixel )
758 alignedOutput[pixel] = AveragePixelRGBA8888( alignedScanline1[pixel], alignedScanline2[pixel] );
762 void AverageScanlinesRGB565( const unsigned char * const scanline1,
763 const unsigned char * const __restrict__ scanline2,
764 unsigned char * const outputScanline,
765 const unsigned int width )
767 DebugAssertDualScanlineParameters( scanline1, scanline2, outputScanline, width * 2 );
768 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
769 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
770 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms." );
772 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
773 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
774 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
776 for( unsigned int pixel = 0; pixel < width; ++pixel )
778 alignedOutput[pixel] = AveragePixelRGB565( alignedScanline1[pixel], alignedScanline2[pixel] );
782 /// Dispatch to pixel format appropriate box filter downscaling functions.
783 void DownscaleInPlacePow2( unsigned char * const pixels,
784 Pixel::Format pixelFormat,
785 unsigned int inputWidth,
786 unsigned int inputHeight,
787 unsigned int desiredWidth,
788 unsigned int desiredHeight,
789 ImageAttributes::ScalingMode scalingMode,
790 ImageAttributes::FilterMode filterMode,
792 unsigned& outHeight )
794 outWidth = inputWidth;
795 outHeight = inputHeight;
796 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
797 if( filterMode == ImageAttributes::Box || filterMode == ImageAttributes::BoxThenNearest || filterMode == ImageAttributes::BoxThenLinear )
799 // Check the pixel format is one that is supported:
800 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
802 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode( scalingMode );
804 if( pixelFormat == Pixel::RGBA8888 )
806 Internal::Platform::DownscaleInPlacePow2RGBA8888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
808 else if( pixelFormat == Pixel::RGB888 )
810 Internal::Platform::DownscaleInPlacePow2RGB888( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
812 else if( pixelFormat == Pixel::RGB565 )
814 Internal::Platform::DownscaleInPlacePow2RGB565( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
816 else if( pixelFormat == Pixel::LA88 )
818 Internal::Platform::DownscaleInPlacePow2ComponentPair( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
820 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
822 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
828 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
832 void DownscaleInPlacePow2RGB888( unsigned char * const pixels,
833 const unsigned int inputWidth,
834 const unsigned int inputHeight,
835 const unsigned int desiredWidth,
836 const unsigned int desiredHeight,
837 BoxDimensionTest dimensionTest,
839 unsigned& outHeight )
841 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
844 void DownscaleInPlacePow2RGBA8888( unsigned char * const pixels,
845 const unsigned int inputWidth,
846 const unsigned int inputHeight,
847 const unsigned int desiredWidth,
848 const unsigned int desiredHeight,
849 BoxDimensionTest dimensionTest,
851 unsigned& outHeight )
853 DALI_ASSERT_DEBUG( ((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms." );
854 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
857 void DownscaleInPlacePow2RGB565( unsigned char * pixels,
858 unsigned int inputWidth,
859 unsigned int inputHeight,
860 unsigned int desiredWidth,
861 unsigned int desiredHeight,
862 BoxDimensionTest dimensionTest,
863 unsigned int& outWidth,
864 unsigned int& outHeight )
866 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
870 * @copydoc DownscaleInPlacePow2RGB888
872 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
874 void DownscaleInPlacePow2ComponentPair( unsigned char * const pixels,
875 const unsigned int inputWidth,
876 const unsigned int inputHeight,
877 const unsigned int desiredWidth,
878 const unsigned int desiredHeight,
879 BoxDimensionTest dimensionTest,
881 unsigned& outHeight )
883 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
886 void DownscaleInPlacePow2SingleBytePerPixel( unsigned char * pixels,
887 unsigned int inputWidth,
888 unsigned int inputHeight,
889 unsigned int desiredWidth,
890 unsigned int desiredHeight,
891 BoxDimensionTest dimensionTest,
892 unsigned int& outWidth,
893 unsigned int& outHeight )
895 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>( pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight );
901 // Point sample an image to a new resolution (like GL_NEAREST).
902 template<typename PIXEL>
903 void PointSampleAddressablePixels( const uint8_t * inPixels,
904 unsigned int inputWidth,
905 unsigned int inputHeight,
907 unsigned int desiredWidth,
908 unsigned int desiredHeight )
910 DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
911 outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
912 "The input and output buffers must not overlap for an upscaling.");
913 DALI_ASSERT_DEBUG( ((uint64_t) inPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
914 DALI_ASSERT_DEBUG( ((uint64_t) outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
916 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
920 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
921 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
922 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
923 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
925 unsigned int inY = 0;
926 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
928 // Round fixed point y coordinate to nearest integer:
929 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
930 const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
931 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
933 DALI_ASSERT_DEBUG( integerY < inputHeight );
934 DALI_ASSERT_DEBUG( reinterpret_cast<const uint8_t*>(inScanline) < ( inPixels + inputWidth * inputHeight * sizeof(PIXEL) ) );
935 DALI_ASSERT_DEBUG( reinterpret_cast<uint8_t*>(outScanline) < ( outPixels + desiredWidth * desiredHeight * sizeof(PIXEL) ) );
937 unsigned int inX = 0;
938 for( unsigned int outX = 0; outX < desiredWidth; ++outX )
940 // Round the fixed-point x coordinate to an integer:
941 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
942 const PIXEL* const inPixelAddress = &inScanline[integerX];
943 const PIXEL pixel = *inPixelAddress;
944 outScanline[outX] = pixel;
956 void PointSample4BPP( const unsigned char * inPixels,
957 unsigned int inputWidth,
958 unsigned int inputHeight,
959 unsigned char * outPixels,
960 unsigned int desiredWidth,
961 unsigned int desiredHeight )
963 PointSampleAddressablePixels<uint32_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
969 void PointSample2BPP( const unsigned char * inPixels,
970 unsigned int inputWidth,
971 unsigned int inputHeight,
972 unsigned char * outPixels,
973 unsigned int desiredWidth,
974 unsigned int desiredHeight )
976 PointSampleAddressablePixels<uint16_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
982 void PointSample1BPP( const unsigned char * inPixels,
983 unsigned int inputWidth,
984 unsigned int inputHeight,
985 unsigned char * outPixels,
986 unsigned int desiredWidth,
987 unsigned int desiredHeight )
989 PointSampleAddressablePixels<uint8_t>( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
994 * RGB888 is a special case as its pixels are not aligned addressable units.
996 void PointSample3BPP( const uint8_t * inPixels,
997 unsigned int inputWidth,
998 unsigned int inputHeight,
1000 unsigned int desiredWidth,
1001 unsigned int desiredHeight )
1003 if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
1007 const unsigned int BYTES_PER_PIXEL = 3;
1009 // Generate fixed-point 16.16 deltas in input image coordinates:
1010 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1011 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1013 // Step through output image in whole integer pixel steps while tracking the
1014 // corresponding locations in the input image using 16.16 fixed-point
1016 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1017 for( unsigned int outY = 0; outY < desiredHeight; ++outY )
1019 const uint8_t* const inScanline = &inPixels[inputWidth * (inY >> 16u) * BYTES_PER_PIXEL];
1020 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1021 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1023 for( unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL )
1025 // Truncate the fixed-point input coordinate to the address of the input pixel to sample:
1026 const uint8_t* const inPixelAddress = &inScanline[(inX >> 16u) * BYTES_PER_PIXEL];
1028 // Issue loads for all pixel color components up-front:
1029 const unsigned int c0 = inPixelAddress[0];
1030 const unsigned int c1 = inPixelAddress[1];
1031 const unsigned int c2 = inPixelAddress[2];
1032 ///@ToDo: Benchmark one 32bit load that will be unaligned 2/3 of the time + 3 rotate and masks, versus these three aligned byte loads.
1034 // Output the pixel components:
1035 outScanline[outX] = c0;
1036 outScanline[outX + 1] = c1;
1037 outScanline[outX + 2] = c2;
1039 // Increment the fixed-point input coordinate:
1047 // Dispatch to a format-appropriate point sampling function:
1048 void PointSample( const unsigned char * inPixels,
1049 unsigned int inputWidth,
1050 unsigned int inputHeight,
1051 Pixel::Format pixelFormat,
1052 unsigned char * outPixels,
1053 unsigned int desiredWidth,
1054 unsigned int desiredHeight )
1056 // Check the pixel format is one that is supported:
1057 if( pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1059 if( pixelFormat == Pixel::RGBA8888 )
1061 PointSample4BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1063 else if( pixelFormat == Pixel::RGB888 )
1065 PointSample3BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1067 else if( pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 )
1069 PointSample2BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1071 else if( pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 )
1073 PointSample1BPP( inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight );
1078 DALI_LOG_INFO( gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat) );
1082 } /* namespace Platform */
1083 } /* namespace Internal */
1084 } /* namespace Dali */