2 * Copyright (c) 2021 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 <dali/internal/imaging/common/image-operations.h>
21 #include <dali/devel-api/adaptor-framework/image-loading.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/common/dali-vector.h>
24 #include <dali/public-api/math/vector2.h>
26 #include <third-party/resampler/resampler.h>
42 // The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
43 // A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
44 // We can optionally use a Vector4 color here, but at reduced fill speed.
45 const uint8_t BORDER_FILL_VALUE(0x00);
46 // A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
47 const unsigned int MAXIMUM_TARGET_BITMAP_SIZE((1u << 16) - 1);
49 // Constants used by the ImageResampler.
50 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.
51 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.
53 const float RAD_135 = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians;
54 const float RAD_225 = RAD_135 + Math::PI_2; ///< 225 degrees in radians;
55 const float RAD_270 = 3.f * Math::PI_2; ///< 270 degrees in radians;
56 const float RAD_315 = RAD_225 + Math::PI_2; ///< 315 degrees in radians;
58 using Integration::Bitmap;
59 using Integration::BitmapPtr;
60 typedef unsigned char PixelBuffer;
63 * @brief 4 byte pixel structure.
71 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
74 * @brief RGB888 pixel structure.
81 } __attribute__((packed, aligned(1)));
84 * @brief RGB565 pixel typedefed from a short.
86 * Access fields by manual shifting and masking.
88 typedef uint16_t PixelRGB565;
91 * @brief a Pixel composed of two independent byte components.
97 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
99 #if defined(DEBUG_ENABLED)
101 * Disable logging of image operations or make it verbose from the commandline
102 * as follows (e.g., for dali demo app):
104 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
105 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
108 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS");
111 /** @return The greatest even number less than or equal to the argument. */
112 inline unsigned int EvenDown(const unsigned int a)
114 const unsigned int evened = a & ~1u;
119 * @brief Log bad parameters.
121 void ValidateScalingParameters(const unsigned int inputWidth,
122 const unsigned int inputHeight,
123 const unsigned int desiredWidth,
124 const unsigned int desiredHeight)
126 if(desiredWidth > inputWidth || desiredHeight > inputHeight)
128 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight);
131 if(desiredWidth == 0u || desiredHeight == 0u)
133 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n");
136 if(inputWidth == 0u || inputHeight == 0u)
138 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n");
143 * @brief Do debug assertions common to all scanline halving functions.
144 * @note Inline and in anon namespace so should boil away in release builds.
146 inline void DebugAssertScanlineParameters(const uint8_t* const pixels, const unsigned int width)
148 DALI_ASSERT_DEBUG(pixels && "Null pointer.");
149 DALI_ASSERT_DEBUG(width > 1u && "Can't average fewer than two pixels.");
150 DALI_ASSERT_DEBUG(width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?");
154 * @brief Assertions on params to functions averaging pairs of scanlines.
155 * @note Inline as intended to boil away in release.
157 inline void DebugAssertDualScanlineParameters(const uint8_t* const scanline1,
158 const uint8_t* const scanline2,
159 uint8_t* const outputScanline,
160 const size_t widthInComponents)
162 DALI_ASSERT_DEBUG(scanline1 && "Null pointer.");
163 DALI_ASSERT_DEBUG(scanline2 && "Null pointer.");
164 DALI_ASSERT_DEBUG(outputScanline && "Null pointer.");
165 DALI_ASSERT_DEBUG(((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents)) && "Scanlines alias.");
166 DALI_ASSERT_DEBUG(((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output.");
170 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
172 BoxDimensionTest DimensionTestForScalingMode(FittingMode::Type fittingMode)
174 BoxDimensionTest dimensionTest;
175 dimensionTest = BoxDimensionTestEither;
179 // Shrink to fit attempts to make one or zero dimensions smaller than the
180 // desired dimensions and one or two dimensions exactly the same as the desired
181 // ones, so as long as one dimension is larger than the desired size, box
182 // filtering can continue even if the second dimension is smaller than the
183 // desired dimensions:
184 case FittingMode::SHRINK_TO_FIT:
186 dimensionTest = BoxDimensionTestEither;
189 // Scale to fill mode keeps both dimensions at least as large as desired:
190 case FittingMode::SCALE_TO_FILL:
192 dimensionTest = BoxDimensionTestBoth;
195 // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
196 case FittingMode::FIT_WIDTH:
198 dimensionTest = BoxDimensionTestX;
201 // X Dimension is ignored by definition in FIT_HEIGHT mode:
202 case FittingMode::FIT_HEIGHT:
204 dimensionTest = BoxDimensionTestY;
209 return dimensionTest;
213 * @brief Work out the dimensions for a uniform scaling of the input to map it
214 * into the target while effecting ShinkToFit scaling mode.
216 ImageDimensions FitForShrinkToFit(ImageDimensions target, ImageDimensions source)
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 SCALE_TO_FILL scaling mode.
235 * @note An image scaled into the output dimensions will need either top and
236 * bottom or left and right to be cropped away unless the source was pre-cropped
237 * to match the destination aspect ratio.
239 ImageDimensions FitForScaleToFill(ImageDimensions target, ImageDimensions source)
241 DALI_ASSERT_DEBUG(source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in");
242 // Scale the input by the least extreme of the two dimensions:
243 const float widthScale = target.GetX() / float(source.GetX());
244 const float heightScale = target.GetY() / float(source.GetY());
245 const float scale = widthScale > heightScale ? widthScale : heightScale;
247 // Do no scaling at all if the result would increase area:
253 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
257 * @brief Work out the dimensions for a uniform scaling of the input to map it
258 * into the target while effecting FIT_WIDTH scaling mode.
260 ImageDimensions FitForFitWidth(ImageDimensions target, ImageDimensions source)
262 DALI_ASSERT_DEBUG(source.GetX() > 0 && "Cant fit a zero-dimension rectangle.");
263 const float scale = target.GetX() / float(source.GetX());
265 // Do no scaling at all if the result would increase area:
270 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
274 * @brief Work out the dimensions for a uniform scaling of the input to map it
275 * into the target while effecting FIT_HEIGHT scaling mode.
277 ImageDimensions FitForFitHeight(ImageDimensions target, ImageDimensions source)
279 DALI_ASSERT_DEBUG(source.GetY() > 0 && "Cant fit a zero-dimension rectangle.");
280 const float scale = target.GetY() / float(source.GetY());
282 // Do no scaling at all if the result would increase area:
288 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
292 * @brief Generate the rectangle to use as the target of a pixel sampling pass
293 * (e.g., nearest or linear).
295 ImageDimensions FitToScalingMode(ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode)
297 ImageDimensions fitDimensions;
300 case FittingMode::SHRINK_TO_FIT:
302 fitDimensions = FitForShrinkToFit(requestedSize, sourceSize);
305 case FittingMode::SCALE_TO_FILL:
307 fitDimensions = FitForScaleToFill(requestedSize, sourceSize);
310 case FittingMode::FIT_WIDTH:
312 fitDimensions = FitForFitWidth(requestedSize, sourceSize);
315 case FittingMode::FIT_HEIGHT:
317 fitDimensions = FitForFitHeight(requestedSize, sourceSize);
322 return fitDimensions;
326 * @brief Calculate the number of lines on the X and Y axis that need to be
327 * either added or removed with repect to the specified fitting mode.
328 * (e.g., nearest or linear).
329 * @param[in] sourceSize The size of the source image
330 * @param[in] fittingMode The fitting mode to use
331 * @param[in/out] requestedSize The target size that the image will be fitted to.
332 * If the source image is smaller than the requested size, the source is not scaled up.
333 * So we reduce the target size while keeping aspect by lowering resolution.
334 * @param[out] scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
335 * @param[out] columnsToCrop The number of columns to remove from the image (can be negative to represent X borders required)
337 void CalculateBordersFromFittingMode(ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop)
339 const int sourceWidth(static_cast<int>(sourceSize.GetWidth()));
340 const int sourceHeight(static_cast<int>(sourceSize.GetHeight()));
341 const float targetAspect(static_cast<float>(requestedSize.GetWidth()) / static_cast<float>(requestedSize.GetHeight()));
347 case FittingMode::FIT_WIDTH:
349 finalWidth = sourceWidth;
350 finalHeight = static_cast<float>(sourceWidth) / targetAspect;
353 scanlinesToCrop = -(finalHeight - sourceHeight);
357 case FittingMode::FIT_HEIGHT:
359 finalWidth = static_cast<float>(sourceHeight) * targetAspect;
360 finalHeight = sourceHeight;
362 columnsToCrop = -(finalWidth - sourceWidth);
367 case FittingMode::SHRINK_TO_FIT:
369 const float sourceAspect(static_cast<float>(sourceWidth) / static_cast<float>(sourceHeight));
370 if(sourceAspect > targetAspect)
372 finalWidth = sourceWidth;
373 finalHeight = static_cast<float>(sourceWidth) / targetAspect;
376 scanlinesToCrop = -(finalHeight - sourceHeight);
380 finalWidth = static_cast<float>(sourceHeight) * targetAspect;
381 finalHeight = sourceHeight;
383 columnsToCrop = -(finalWidth - sourceWidth);
389 case FittingMode::SCALE_TO_FILL:
391 const float sourceAspect(static_cast<float>(sourceWidth) / static_cast<float>(sourceHeight));
392 if(sourceAspect > targetAspect)
394 finalWidth = static_cast<float>(sourceHeight) * targetAspect;
395 finalHeight = sourceHeight;
397 columnsToCrop = -(finalWidth - sourceWidth);
402 finalWidth = sourceWidth;
403 finalHeight = static_cast<float>(sourceWidth) / targetAspect;
406 scanlinesToCrop = -(finalHeight - sourceHeight);
412 requestedSize.SetWidth(finalWidth);
413 requestedSize.SetHeight(finalHeight);
417 * @brief Construct a pixel buffer object from a copy of the pixel array passed in.
419 Dali::Devel::PixelBuffer MakePixelBuffer(const uint8_t* const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height)
421 DALI_ASSERT_DEBUG(pixels && "Null bitmap buffer to copy.");
423 // Allocate a pixel buffer to hold the image passed in:
424 auto newBitmap = Dali::Devel::PixelBuffer::New(width, height, pixelFormat);
426 // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
427 memcpy(newBitmap.GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel(pixelFormat));
432 * @brief Work out the desired width and height, accounting for zeros.
434 * @param[in] bitmapWidth Width of image before processing.
435 * @param[in] bitmapHeight Height of image before processing.
436 * @param[in] requestedWidth Width of area to scale image into. Can be zero.
437 * @param[in] requestedHeight Height of area to scale image into. Can be zero.
438 * @return Dimensions of area to scale image into after special rules are applied.
440 ImageDimensions CalculateDesiredDimensions(unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight)
442 unsigned int maxSize = Dali::GetMaxTextureSize();
444 // If no dimensions have been requested, default to the source ones:
445 if(requestedWidth == 0 && requestedHeight == 0)
447 if(bitmapWidth <= maxSize && bitmapHeight <= maxSize)
449 return ImageDimensions(bitmapWidth, bitmapHeight);
453 // Calculate the size from the max texture size and the source image aspect ratio
454 if(bitmapWidth > bitmapHeight)
456 return ImageDimensions(maxSize, bitmapHeight * maxSize / static_cast<float>(bitmapWidth) + 0.5f);
460 return ImageDimensions(bitmapWidth * maxSize / static_cast<float>(bitmapHeight) + 0.5f, maxSize);
465 // If both dimensions have values requested, use them both:
466 if(requestedWidth != 0 && requestedHeight != 0)
468 if(requestedWidth <= maxSize && requestedHeight <= maxSize)
470 return ImageDimensions(requestedWidth, requestedHeight);
474 // Calculate the size from the max texture size and the source image aspect ratio
475 if(requestedWidth > requestedHeight)
477 return ImageDimensions(maxSize, requestedHeight * maxSize / static_cast<float>(requestedWidth) + 0.5f);
481 return ImageDimensions(requestedWidth * maxSize / static_cast<float>(requestedHeight) + 0.5f, maxSize);
486 // Only one of the dimensions has been requested. Calculate the other from
487 // the requested one and the source image aspect ratio:
488 if(requestedWidth != 0)
490 requestedWidth = std::min(requestedWidth, maxSize);
491 return ImageDimensions(requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f);
494 requestedHeight = std::min(requestedHeight, maxSize);
495 return ImageDimensions(bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight);
499 * @brief Rotates the given buffer @p pixelsIn 90 degrees counter clockwise.
501 * @note It allocates memory for the returned @p pixelsOut buffer.
502 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
503 * @note It may fail if malloc() fails to allocate memory.
505 * @param[in] pixelsIn The input buffer.
506 * @param[in] widthIn The width of the input buffer.
507 * @param[in] heightIn The height of the input buffer.
508 * @param[in] pixelSize The size of the pixel.
509 * @param[out] pixelsOut The rotated output buffer.
510 * @param[out] widthOut The width of the output buffer.
511 * @param[out] heightOut The height of the output buffer.
513 * @return Whether the rotation succeded.
515 bool Rotate90(const uint8_t* const pixelsIn,
516 unsigned int widthIn,
517 unsigned int heightIn,
518 unsigned int pixelSize,
520 unsigned int& widthOut,
521 unsigned int& heightOut)
523 // The new size of the image.
527 // Allocate memory for the rotated buffer.
528 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
529 if(nullptr == pixelsOut)
534 // Return if the memory allocations fails.
538 // Rotate the buffer.
539 for(unsigned int y = 0u; y < heightIn; ++y)
541 const unsigned int srcLineIndex = y * widthIn;
542 const unsigned int dstX = y;
543 for(unsigned int x = 0u; x < widthIn; ++x)
545 const unsigned int dstY = heightOut - x - 1u;
546 const unsigned int dstIndex = pixelSize * (dstY * widthOut + dstX);
547 const unsigned int srcIndex = pixelSize * (srcLineIndex + x);
549 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
551 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
560 * @brief Rotates the given buffer @p pixelsIn 180 degrees counter clockwise.
562 * @note It allocates memory for the returned @p pixelsOut buffer.
563 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
564 * @note It may fail if malloc() fails to allocate memory.
566 * @param[in] pixelsIn The input buffer.
567 * @param[in] widthIn The width of the input buffer.
568 * @param[in] heightIn The height of the input buffer.
569 * @param[in] pixelSize The size of the pixel.
570 * @param[out] pixelsOut The rotated output buffer.
572 * @return Whether the rotation succeded.
574 bool Rotate180(const uint8_t* const pixelsIn,
575 unsigned int widthIn,
576 unsigned int heightIn,
577 unsigned int pixelSize,
580 // Allocate memory for the rotated buffer.
581 pixelsOut = static_cast<uint8_t*>(malloc(widthIn * heightIn * pixelSize));
582 if(nullptr == pixelsOut)
584 // Return if the memory allocations fails.
588 // Rotate the buffer.
589 for(unsigned int y = 0u; y < heightIn; ++y)
591 const unsigned int srcLineIndex = y * widthIn;
592 const unsigned int dstY = heightIn - y - 1u;
593 for(unsigned int x = 0u; x < widthIn; ++x)
595 const unsigned int dstX = widthIn - x - 1u;
596 const unsigned int dstIndex = pixelSize * (dstY * widthIn + dstX);
597 const unsigned int srcIndex = pixelSize * (srcLineIndex + x);
599 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
601 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
610 * @brief Rotates the given buffer @p pixelsIn 270 degrees counter clockwise.
612 * @note It allocates memory for the returned @p pixelsOut buffer.
613 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
614 * @note It may fail if malloc() fails to allocate memory.
616 * @param[in] pixelsIn The input buffer.
617 * @param[in] widthIn The width of the input buffer.
618 * @param[in] heightIn The height of the input buffer.
619 * @param[in] pixelSize The size of the pixel.
620 * @param[out] pixelsOut The rotated output buffer.
621 * @param[out] widthOut The width of the output buffer.
622 * @param[out] heightOut The height of the output buffer.
624 * @return Whether the rotation succeded.
626 bool Rotate270(const uint8_t* const pixelsIn,
627 unsigned int widthIn,
628 unsigned int heightIn,
629 unsigned int pixelSize,
631 unsigned int& widthOut,
632 unsigned int& heightOut)
634 // The new size of the image.
638 // Allocate memory for the rotated buffer.
639 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
640 if(nullptr == pixelsOut)
645 // Return if the memory allocations fails.
649 // Rotate the buffer.
650 for(unsigned int y = 0u; y < heightIn; ++y)
652 const unsigned int srcLineIndex = y * widthIn;
653 const unsigned int dstX = widthOut - y - 1u;
654 for(unsigned int x = 0u; x < widthIn; ++x)
656 const unsigned int dstY = x;
657 const unsigned int dstIndex = pixelSize * (dstY * widthOut + dstX);
658 const unsigned int srcIndex = pixelSize * (srcLineIndex + x);
660 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
662 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
671 * @brief Skews a row horizontally (with filtered weights)
673 * @note Limited to 45 degree skewing only.
674 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
676 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
677 * @param[in] srcWidth The width of the input pixel buffer.
678 * @param[in] pixelSize The size of the pixel.
679 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
680 * @param[in] dstWidth The width of the output pixel buffer.
681 * @param[in] row The row index.
682 * @param[in] offset The skew offset.
683 * @param[in] weight The relative weight of right pixel.
685 void HorizontalSkew(const uint8_t* const srcBufferPtr,
687 unsigned int pixelSize,
688 uint8_t*& dstBufferPtr,
696 // Fill gap left of skew with background.
697 memset(dstBufferPtr + row * pixelSize * dstWidth, 0u, pixelSize * offset);
700 unsigned char oldLeft[4u] = {0u, 0u, 0u, 0u};
703 for(i = 0u; i < srcWidth; ++i)
705 // Loop through row pixels
706 const unsigned int srcIndex = pixelSize * (row * srcWidth + i);
708 unsigned char src[4u] = {0u, 0u, 0u, 0u};
709 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
711 src[channel] = *(srcBufferPtr + srcIndex + channel);
715 unsigned char left[4u] = {0u, 0u, 0u, 0u};
716 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
718 left[channel] = static_cast<unsigned char>(static_cast<float>(src[channel]) * weight);
720 // Update left over on source
721 src[channel] -= (left[channel] - oldLeft[channel]);
725 if((i + offset >= 0) && (i + offset < dstWidth))
727 const unsigned int dstIndex = pixelSize * (row * dstWidth + i + offset);
729 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
731 *(dstBufferPtr + dstIndex + channel) = src[channel];
735 // Save leftover for next pixel in scan
736 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
738 oldLeft[channel] = left[channel];
742 // Go to rightmost point of skew
746 // If still in image bounds, put leftovers there
747 const unsigned int dstIndex = pixelSize * (row * dstWidth + i);
749 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
751 *(dstBufferPtr + dstIndex + channel) = oldLeft[channel];
754 // Clear to the right of the skewed line with background
756 memset(dstBufferPtr + pixelSize * (row * dstWidth + i), 0u, pixelSize * (dstWidth - i));
761 * @brief Skews a column vertically (with filtered weights)
763 * @note Limited to 45 degree skewing only.
764 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
766 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
767 * @param[in] srcWidth The width of the input pixel buffer.
768 * @param[in] srcHeight The height of the input pixel buffer.
769 * @param[in] pixelSize The size of the pixel.
770 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
771 * @param[in] dstWidth The width of the output pixel buffer.
772 * @param[in] dstHeight The height of the output pixel buffer.
773 * @param[in] column The column index.
774 * @param[in] offset The skew offset.
775 * @param[in] weight The relative weight of uppeer pixel.
777 void VerticalSkew(const uint8_t* const srcBufferPtr,
780 unsigned int pixelSize,
781 uint8_t*& dstBufferPtr,
788 for(int i = 0; i < offset; ++i)
790 // Fill gap above skew with background
791 const unsigned int dstIndex = pixelSize * (i * dstWidth + column);
793 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
795 *(dstBufferPtr + dstIndex + channel) = 0u;
799 unsigned char oldLeft[4u] = {0u, 0u, 0u, 0u};
803 for(i = 0; i < srcHeight; ++i)
805 // Loop through column pixels
806 const unsigned int srcIndex = pixelSize * (i * srcWidth + column);
808 unsigned char src[4u] = {0u, 0u, 0u, 0u};
809 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
811 src[channel] = *(srcBufferPtr + srcIndex + channel);
817 unsigned char left[4u] = {0u, 0u, 0u, 0u};
818 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
820 left[channel] = static_cast<unsigned char>(static_cast<float>(src[channel]) * weight);
821 // Update left over on source
822 src[channel] -= (left[channel] - oldLeft[channel]);
826 if((yPos >= 0) && (yPos < dstHeight))
828 const unsigned int dstIndex = pixelSize * (yPos * dstWidth + column);
830 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
832 *(dstBufferPtr + dstIndex + channel) = src[channel];
836 // Save leftover for next pixel in scan
837 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
839 oldLeft[channel] = left[channel];
843 // Go to bottom point of skew
847 // If still in image bounds, put leftovers there
848 const unsigned int dstIndex = pixelSize * (i * dstWidth + column);
850 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
852 *(dstBufferPtr + dstIndex + channel) = oldLeft[channel];
856 while(++i < dstHeight)
858 // Clear below skewed line with background
859 const unsigned int dstIndex = pixelSize * (i * dstWidth + column);
861 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
863 *(dstBufferPtr + dstIndex + channel) = 0u;
870 ImageDimensions CalculateDesiredDimensions(ImageDimensions rawDimensions, ImageDimensions requestedDimensions)
872 return CalculateDesiredDimensions(rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight());
876 * @brief Apply cropping and padding for specified fitting mode.
878 * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
879 * based on the fitting mode.
881 * This will add vertical or horizontal borders if necessary.
882 * Crop the source image data vertically or horizontally if necessary.
883 * The aspect of the source image is preserved.
884 * If the source image is smaller than the desired size, the algorithm will modify the the newly created
885 * bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
886 * GPU scaling to be performed at render time giving the same result with less texture traversal.
888 * @param[in] bitmap The source pixel buffer to perform modifications on.
889 * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
890 * @param[in] fittingMode The fitting mode to use.
892 * @return A new bitmap with the padding and cropping required for fitting mode applied.
893 * If no modification is needed or possible, the passed in bitmap is returned.
895 Dali::Devel::PixelBuffer CropAndPadForFittingMode(Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode);
898 * @brief Adds horizontal or vertical borders to the source image.
900 * @param[in] targetPixels The destination image pointer to draw the borders on.
901 * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer.
902 * @param[in] targetDimensions The dimensions of the destination image.
903 * @param[in] padDimensions The columns and scanlines to pad with borders.
905 void AddBorders(PixelBuffer* targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions);
907 Dali::Devel::PixelBuffer ApplyAttributesToBitmap(Dali::Devel::PixelBuffer bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode)
911 // Calculate the desired box, accounting for a possible zero component:
912 const ImageDimensions desiredDimensions = CalculateDesiredDimensions(bitmap.GetWidth(), bitmap.GetHeight(), dimensions.GetWidth(), dimensions.GetHeight());
914 // If a different size than the raw one has been requested, resize the image
915 // maximally using a repeated box filter without making it smaller than the
916 // requested size in either dimension:
917 bitmap = DownscaleBitmap(bitmap, desiredDimensions, fittingMode, samplingMode);
919 // Cut the bitmap according to the desired width and height so that the
920 // resulting bitmap has the same aspect ratio as the desired dimensions.
921 // Add crop and add borders if necessary depending on fitting mode.
924 bitmap = CropAndPadForFittingMode(bitmap, desiredDimensions, fittingMode);
931 Dali::Devel::PixelBuffer CropAndPadForFittingMode(Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode)
933 const unsigned int inputWidth = bitmap.GetWidth();
934 const unsigned int inputHeight = bitmap.GetHeight();
936 if(desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u)
938 DALI_LOG_WARNING("Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight());
940 else if(inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight())
942 // Calculate any padding or cropping that needs to be done based on the fitting mode.
943 // Note: If the desired size is larger than the original image, the desired size will be
944 // reduced while maintaining the aspect, in order to save unnecessary memory usage.
945 int scanlinesToCrop = 0;
946 int columnsToCrop = 0;
948 CalculateBordersFromFittingMode(ImageDimensions(inputWidth, inputHeight), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop);
950 unsigned int desiredWidth(desiredDimensions.GetWidth());
951 unsigned int desiredHeight(desiredDimensions.GetHeight());
953 // Action the changes by making a new bitmap with the central part of the loaded one if required.
954 if(scanlinesToCrop != 0 || columnsToCrop != 0)
956 // Split the adding and removing of scanlines and columns into separate variables,
957 // so we can use one piece of generic code to action the changes.
958 unsigned int scanlinesToPad = 0;
959 unsigned int columnsToPad = 0;
960 if(scanlinesToCrop < 0)
962 scanlinesToPad = -scanlinesToCrop;
965 if(columnsToCrop < 0)
967 columnsToPad = -columnsToCrop;
971 // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
972 if((desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE) || (desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE) ||
973 (columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE) || (scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE))
975 DALI_LOG_WARNING("Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight);
979 // Create new PixelBuffer with the desired size.
980 const auto pixelFormat = bitmap.GetPixelFormat();
982 auto croppedBitmap = Devel::PixelBuffer::New(desiredWidth, desiredHeight, pixelFormat);
984 // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
985 // The cropping is added to the source pointer, and the padding is added to the destination.
986 const auto bytesPerPixel = Pixel::GetBytesPerPixel(pixelFormat);
987 const PixelBuffer* const sourcePixels = bitmap.GetBuffer() + ((((scanlinesToCrop / 2) * inputWidth) + (columnsToCrop / 2)) * bytesPerPixel);
988 PixelBuffer* const targetPixels = croppedBitmap.GetBuffer();
989 PixelBuffer* const targetPixelsActive = targetPixels + ((((scanlinesToPad / 2) * desiredWidth) + (columnsToPad / 2)) * bytesPerPixel);
990 DALI_ASSERT_DEBUG(sourcePixels && targetPixels);
992 // Copy the image data to the new bitmap.
993 // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
994 unsigned int outputSpan(desiredWidth * bytesPerPixel);
995 if(columnsToCrop == 0 && columnsToPad == 0)
997 memcpy(targetPixelsActive, sourcePixels, (desiredHeight - scanlinesToPad) * outputSpan);
1001 // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
1002 // Precalculate any constants to optimize the inner loop.
1003 const unsigned int inputSpan(inputWidth * bytesPerPixel);
1004 const unsigned int copySpan((desiredWidth - columnsToPad) * bytesPerPixel);
1005 const unsigned int scanlinesToCopy(desiredHeight - scanlinesToPad);
1007 for(unsigned int y = 0; y < scanlinesToCopy; ++y)
1009 memcpy(&targetPixelsActive[y * outputSpan], &sourcePixels[y * inputSpan], copySpan);
1013 // Add vertical or horizontal borders to the final image (if required).
1014 desiredDimensions.SetWidth(desiredWidth);
1015 desiredDimensions.SetHeight(desiredHeight);
1016 AddBorders(croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions(columnsToPad, scanlinesToPad));
1017 // Overwrite the loaded bitmap with the cropped version
1018 bitmap = croppedBitmap;
1025 void AddBorders(PixelBuffer* targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions)
1027 // Assign ints for faster access.
1028 unsigned int desiredWidth(targetDimensions.GetWidth());
1029 unsigned int desiredHeight(targetDimensions.GetHeight());
1030 unsigned int columnsToPad(padDimensions.GetWidth());
1031 unsigned int scanlinesToPad(padDimensions.GetHeight());
1032 unsigned int outputSpan(desiredWidth * bytesPerPixel);
1034 // Add letterboxing (symmetrical borders) if needed.
1035 if(scanlinesToPad > 0)
1037 // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
1038 memset(targetPixels, BORDER_FILL_VALUE, (scanlinesToPad / 2) * outputSpan);
1040 // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
1041 // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
1042 unsigned int bottomBorderHeight = scanlinesToPad - (scanlinesToPad / 2);
1045 memset(&targetPixels[(desiredHeight - bottomBorderHeight) * outputSpan], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan);
1047 else if(columnsToPad > 0)
1049 // Add a left and right border.
1051 // Pre-calculate span size outside of loop.
1052 unsigned int leftBorderSpanWidth((columnsToPad / 2) * bytesPerPixel);
1053 for(unsigned int y = 0; y < desiredHeight; ++y)
1055 memset(&targetPixels[y * outputSpan], BORDER_FILL_VALUE, leftBorderSpanWidth);
1059 // Pre-calculate the initial x offset as it is always the same for a small optimization.
1060 // We subtract columnsToPad/2 from columnsToPad so that we have the correct
1061 // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
1062 unsigned int rightBorderWidth = columnsToPad - (columnsToPad / 2);
1063 PixelBuffer* const destPixelsRightBorder(targetPixels + ((desiredWidth - rightBorderWidth) * bytesPerPixel));
1064 unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
1066 for(unsigned int y = 0; y < desiredHeight; ++y)
1068 memset(&destPixelsRightBorder[y * outputSpan], BORDER_FILL_VALUE, rightBorderSpanWidth);
1073 Dali::Devel::PixelBuffer DownscaleBitmap(Dali::Devel::PixelBuffer bitmap,
1074 ImageDimensions desired,
1075 FittingMode::Type fittingMode,
1076 SamplingMode::Type samplingMode)
1078 // Source dimensions as loaded from resources (e.g. filesystem):
1079 auto bitmapWidth = bitmap.GetWidth();
1080 auto bitmapHeight = bitmap.GetHeight();
1081 // Desired dimensions (the rectangle to fit the source image to):
1082 auto desiredWidth = desired.GetWidth();
1083 auto desiredHeight = desired.GetHeight();
1085 Dali::Devel::PixelBuffer outputBitmap{bitmap};
1087 // If a different size than the raw one has been requested, resize the image:
1089 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
1090 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)))
1092 auto pixelFormat = bitmap.GetPixelFormat();
1094 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
1095 unsigned int shrunkWidth = -1, shrunkHeight = -1;
1096 DownscaleInPlacePow2(bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight);
1098 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
1099 const ImageDimensions filteredDimensions = FitToScalingMode(ImageDimensions(desiredWidth, desiredHeight), ImageDimensions(shrunkWidth, shrunkHeight), fittingMode);
1100 const unsigned int filteredWidth = filteredDimensions.GetWidth();
1101 const unsigned int filteredHeight = filteredDimensions.GetHeight();
1103 // Run a filter to scale down the bitmap if it needs it:
1104 bool filtered = false;
1105 if(filteredWidth < shrunkWidth || filteredHeight < shrunkHeight)
1107 if(samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
1108 samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST)
1110 outputBitmap = Dali::Devel::PixelBuffer::New(filteredWidth, filteredHeight, pixelFormat);
1114 if(samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR)
1116 LinearSample(bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap.GetBuffer(), filteredDimensions);
1120 PointSample(bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap.GetBuffer(), filteredWidth, filteredHeight);
1126 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
1127 if(filtered == false && (shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight))
1129 outputBitmap = MakePixelBuffer(bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight);
1133 return outputBitmap;
1139 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
1140 * @param test Which combination of the two dimensions matter for terminating the filtering.
1141 * @param scaledWidth The width of the current downscaled image.
1142 * @param scaledHeight The height of the current downscaled image.
1143 * @param desiredWidth The target width for the downscaling.
1144 * @param desiredHeight The target height for the downscaling.
1146 bool ContinueScaling(BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight)
1148 bool keepScaling = false;
1149 const unsigned int nextWidth = scaledWidth >> 1u;
1150 const unsigned int nextHeight = scaledHeight >> 1u;
1152 if(nextWidth >= 1u && nextHeight >= 1u)
1156 case BoxDimensionTestEither:
1158 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
1161 case BoxDimensionTestBoth:
1163 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
1166 case BoxDimensionTestX:
1168 keepScaling = nextWidth >= desiredWidth;
1171 case BoxDimensionTestY:
1173 keepScaling = nextHeight >= desiredHeight;
1183 * @brief A shared implementation of the overall iterative box filter
1184 * downscaling algorithm.
1186 * Specialise this for particular pixel formats by supplying the number of bytes
1187 * per pixel and two functions: one for averaging pairs of neighbouring pixels
1188 * on a single scanline, and a second for averaging pixels at corresponding
1189 * positions on different scanlines.
1192 int BYTES_PER_PIXEL,
1193 void (*HalveScanlineInPlace)(unsigned char* const pixels, const unsigned int width),
1194 void (*AverageScanlines)(const unsigned char* const scanline1, const unsigned char* const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width)>
1195 void DownscaleInPlacePow2Generic(unsigned char* const pixels,
1196 const unsigned int inputWidth,
1197 const unsigned int inputHeight,
1198 const unsigned int desiredWidth,
1199 const unsigned int desiredHeight,
1200 BoxDimensionTest dimensionTest,
1202 unsigned& outHeight)
1208 ValidateScalingParameters(inputWidth, inputHeight, desiredWidth, desiredHeight);
1210 // Scale the image until it would be smaller than desired, stopping if the
1211 // resulting height or width would be less than 1:
1212 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
1213 while(ContinueScaling(dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight))
1215 const unsigned int lastWidth = scaledWidth;
1217 scaledHeight >>= 1u;
1219 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight);
1221 const unsigned int lastScanlinePair = scaledHeight - 1;
1223 // Scale pairs of scanlines until any spare one at the end is dropped:
1224 for(unsigned int y = 0; y <= lastScanlinePair; ++y)
1226 // Scale two scanlines horizontally:
1227 HalveScanlineInPlace(&pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth);
1228 HalveScanlineInPlace(&pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth);
1230 // Scale vertical pairs of pixels while the last two scanlines are still warm in
1231 // the CPU cache(s):
1232 // Note, better access patterns for cache-coherence are possible for very large
1233 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
1234 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
1236 &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
1237 &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
1238 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
1243 ///@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.
1244 outWidth = scaledWidth;
1245 outHeight = scaledHeight;
1250 void HalveScanlineInPlaceRGB888(unsigned char* const pixels, const unsigned int width)
1252 DebugAssertScanlineParameters(pixels, width);
1254 const unsigned int lastPair = EvenDown(width - 2);
1256 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1258 // Load all the byte pixel components we need:
1259 const unsigned int c11 = pixels[pixel * 3];
1260 const unsigned int c12 = pixels[pixel * 3 + 1];
1261 const unsigned int c13 = pixels[pixel * 3 + 2];
1262 const unsigned int c21 = pixels[pixel * 3 + 3];
1263 const unsigned int c22 = pixels[pixel * 3 + 4];
1264 const unsigned int c23 = pixels[pixel * 3 + 5];
1266 // Save the averaged byte pixel components:
1267 pixels[outPixel * 3] = static_cast<unsigned char>(AverageComponent(c11, c21));
1268 pixels[outPixel * 3 + 1] = static_cast<unsigned char>(AverageComponent(c12, c22));
1269 pixels[outPixel * 3 + 2] = static_cast<unsigned char>(AverageComponent(c13, c23));
1273 void HalveScanlineInPlaceRGBA8888(unsigned char* const pixels, const unsigned int width)
1275 DebugAssertScanlineParameters(pixels, width);
1276 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1278 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
1280 const unsigned int lastPair = EvenDown(width - 2);
1282 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1284 const uint32_t averaged = AveragePixelRGBA8888(alignedPixels[pixel], alignedPixels[pixel + 1]);
1285 alignedPixels[outPixel] = averaged;
1289 void HalveScanlineInPlaceRGB565(unsigned char* pixels, unsigned int width)
1291 DebugAssertScanlineParameters(pixels, width);
1292 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1294 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
1296 const unsigned int lastPair = EvenDown(width - 2);
1298 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1300 const uint32_t averaged = AveragePixelRGB565(alignedPixels[pixel], alignedPixels[pixel + 1]);
1301 alignedPixels[outPixel] = averaged;
1305 void HalveScanlineInPlace2Bytes(unsigned char* const pixels, const unsigned int width)
1307 DebugAssertScanlineParameters(pixels, width);
1309 const unsigned int lastPair = EvenDown(width - 2);
1311 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1313 // Load all the byte pixel components we need:
1314 const unsigned int c11 = pixels[pixel * 2];
1315 const unsigned int c12 = pixels[pixel * 2 + 1];
1316 const unsigned int c21 = pixels[pixel * 2 + 2];
1317 const unsigned int c22 = pixels[pixel * 2 + 3];
1319 // Save the averaged byte pixel components:
1320 pixels[outPixel * 2] = static_cast<unsigned char>(AverageComponent(c11, c21));
1321 pixels[outPixel * 2 + 1] = static_cast<unsigned char>(AverageComponent(c12, c22));
1325 void HalveScanlineInPlace1Byte(unsigned char* const pixels, const unsigned int width)
1327 DebugAssertScanlineParameters(pixels, width);
1329 const unsigned int lastPair = EvenDown(width - 2);
1331 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1333 // Load all the byte pixel components we need:
1334 const unsigned int c1 = pixels[pixel];
1335 const unsigned int c2 = pixels[pixel + 1];
1337 // Save the averaged byte pixel component:
1338 pixels[outPixel] = static_cast<unsigned char>(AverageComponent(c1, c2));
1343 * @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.
1344 * 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.
1346 void AverageScanlines1(const unsigned char* const scanline1,
1347 const unsigned char* const __restrict__ scanline2,
1348 unsigned char* const outputScanline,
1349 const unsigned int width)
1351 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width);
1353 for(unsigned int component = 0; component < width; ++component)
1355 outputScanline[component] = static_cast<unsigned char>(AverageComponent(scanline1[component], scanline2[component]));
1359 void AverageScanlines2(const unsigned char* const scanline1,
1360 const unsigned char* const __restrict__ scanline2,
1361 unsigned char* const outputScanline,
1362 const unsigned int width)
1364 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 2);
1366 for(unsigned int component = 0; component < width * 2; ++component)
1368 outputScanline[component] = static_cast<unsigned char>(AverageComponent(scanline1[component], scanline2[component]));
1372 void AverageScanlines3(const unsigned char* const scanline1,
1373 const unsigned char* const __restrict__ scanline2,
1374 unsigned char* const outputScanline,
1375 const unsigned int width)
1377 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 3);
1379 for(unsigned int component = 0; component < width * 3; ++component)
1381 outputScanline[component] = static_cast<unsigned char>(AverageComponent(scanline1[component], scanline2[component]));
1385 void AverageScanlinesRGBA8888(const unsigned char* const scanline1,
1386 const unsigned char* const __restrict__ scanline2,
1387 unsigned char* const outputScanline,
1388 const unsigned int width)
1390 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 4);
1391 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1392 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1393 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1395 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1396 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1397 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1399 for(unsigned int pixel = 0; pixel < width; ++pixel)
1401 alignedOutput[pixel] = AveragePixelRGBA8888(alignedScanline1[pixel], alignedScanline2[pixel]);
1405 void AverageScanlinesRGB565(const unsigned char* const scanline1,
1406 const unsigned char* const __restrict__ scanline2,
1407 unsigned char* const outputScanline,
1408 const unsigned int width)
1410 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 2);
1411 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1412 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1413 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1415 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1416 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1417 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1419 for(unsigned int pixel = 0; pixel < width; ++pixel)
1421 alignedOutput[pixel] = AveragePixelRGB565(alignedScanline1[pixel], alignedScanline2[pixel]);
1425 /// Dispatch to pixel format appropriate box filter downscaling functions.
1426 void DownscaleInPlacePow2(unsigned char* const pixels,
1427 Pixel::Format pixelFormat,
1428 unsigned int inputWidth,
1429 unsigned int inputHeight,
1430 unsigned int desiredWidth,
1431 unsigned int desiredHeight,
1432 FittingMode::Type fittingMode,
1433 SamplingMode::Type samplingMode,
1435 unsigned& outHeight)
1437 outWidth = inputWidth;
1438 outHeight = inputHeight;
1439 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1440 if(samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR)
1442 // Check the pixel format is one that is supported:
1443 if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1445 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode(fittingMode);
1447 if(pixelFormat == Pixel::RGBA8888)
1449 Internal::Platform::DownscaleInPlacePow2RGBA8888(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1451 else if(pixelFormat == Pixel::RGB888)
1453 Internal::Platform::DownscaleInPlacePow2RGB888(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1455 else if(pixelFormat == Pixel::RGB565)
1457 Internal::Platform::DownscaleInPlacePow2RGB565(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1459 else if(pixelFormat == Pixel::LA88)
1461 Internal::Platform::DownscaleInPlacePow2ComponentPair(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1463 else if(pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1465 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1469 DALI_ASSERT_DEBUG(false && "Inner branch conditions don't match outer branch.");
1475 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat));
1479 void DownscaleInPlacePow2RGB888(unsigned char* pixels,
1480 unsigned int inputWidth,
1481 unsigned int inputHeight,
1482 unsigned int desiredWidth,
1483 unsigned int desiredHeight,
1484 BoxDimensionTest dimensionTest,
1486 unsigned& outHeight)
1488 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1491 void DownscaleInPlacePow2RGBA8888(unsigned char* pixels,
1492 unsigned int inputWidth,
1493 unsigned int inputHeight,
1494 unsigned int desiredWidth,
1495 unsigned int desiredHeight,
1496 BoxDimensionTest dimensionTest,
1498 unsigned& outHeight)
1500 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1501 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1504 void DownscaleInPlacePow2RGB565(unsigned char* pixels,
1505 unsigned int inputWidth,
1506 unsigned int inputHeight,
1507 unsigned int desiredWidth,
1508 unsigned int desiredHeight,
1509 BoxDimensionTest dimensionTest,
1510 unsigned int& outWidth,
1511 unsigned int& outHeight)
1513 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1517 * @copydoc DownscaleInPlacePow2RGB888
1519 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1521 void DownscaleInPlacePow2ComponentPair(unsigned char* pixels,
1522 unsigned int inputWidth,
1523 unsigned int inputHeight,
1524 unsigned int desiredWidth,
1525 unsigned int desiredHeight,
1526 BoxDimensionTest dimensionTest,
1528 unsigned& outHeight)
1530 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1533 void DownscaleInPlacePow2SingleBytePerPixel(unsigned char* pixels,
1534 unsigned int inputWidth,
1535 unsigned int inputHeight,
1536 unsigned int desiredWidth,
1537 unsigned int desiredHeight,
1538 BoxDimensionTest dimensionTest,
1539 unsigned int& outWidth,
1540 unsigned int& outHeight)
1542 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1548 * @brief Point sample an image to a new resolution (like GL_NEAREST).
1550 * Template is used purely as a type-safe code generator in this one
1551 * compilation unit. Generated code is inlined into type-specific wrapper
1552 * functions below which are exported to rest of module.
1554 template<typename PIXEL>
1555 inline void PointSampleAddressablePixels(const uint8_t* inPixels,
1556 unsigned int inputWidth,
1557 unsigned int inputHeight,
1559 unsigned int desiredWidth,
1560 unsigned int desiredHeight)
1562 DALI_ASSERT_DEBUG(((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1563 outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1564 "The input and output buffers must not overlap for an upscaling.");
1565 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, ...).");
1566 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, ...).");
1568 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1572 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1573 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1574 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1575 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1577 unsigned int inY = 0;
1578 for(unsigned int outY = 0; outY < desiredHeight; ++outY)
1580 // Round fixed point y coordinate to nearest integer:
1581 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1582 const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1583 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1585 DALI_ASSERT_DEBUG(integerY < inputHeight);
1586 DALI_ASSERT_DEBUG(reinterpret_cast<const uint8_t*>(inScanline) < (inPixels + inputWidth * inputHeight * sizeof(PIXEL)));
1587 DALI_ASSERT_DEBUG(reinterpret_cast<uint8_t*>(outScanline) < (outPixels + desiredWidth * desiredHeight * sizeof(PIXEL)));
1589 unsigned int inX = 0;
1590 for(unsigned int outX = 0; outX < desiredWidth; ++outX)
1592 // Round the fixed-point x coordinate to an integer:
1593 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1594 const PIXEL* const inPixelAddress = &inScanline[integerX];
1595 const PIXEL pixel = *inPixelAddress;
1596 outScanline[outX] = pixel;
1606 void PointSample4BPP(const unsigned char* inPixels,
1607 unsigned int inputWidth,
1608 unsigned int inputHeight,
1609 unsigned char* outPixels,
1610 unsigned int desiredWidth,
1611 unsigned int desiredHeight)
1613 PointSampleAddressablePixels<uint32_t>(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1617 void PointSample2BPP(const unsigned char* inPixels,
1618 unsigned int inputWidth,
1619 unsigned int inputHeight,
1620 unsigned char* outPixels,
1621 unsigned int desiredWidth,
1622 unsigned int desiredHeight)
1624 PointSampleAddressablePixels<uint16_t>(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1628 void PointSample1BPP(const unsigned char* inPixels,
1629 unsigned int inputWidth,
1630 unsigned int inputHeight,
1631 unsigned char* outPixels,
1632 unsigned int desiredWidth,
1633 unsigned int desiredHeight)
1635 PointSampleAddressablePixels<uint8_t>(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1639 * RGB888 is a special case as its pixels are not aligned addressable units.
1641 void PointSample3BPP(const uint8_t* inPixels,
1642 unsigned int inputWidth,
1643 unsigned int inputHeight,
1645 unsigned int desiredWidth,
1646 unsigned int desiredHeight)
1648 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1652 const unsigned int BYTES_PER_PIXEL = 3;
1654 // Generate fixed-point 16.16 deltas in input image coordinates:
1655 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1656 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1658 // Step through output image in whole integer pixel steps while tracking the
1659 // corresponding locations in the input image using 16.16 fixed-point
1661 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1662 for(unsigned int outY = 0; outY < desiredHeight; ++outY)
1664 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1665 const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1666 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1667 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1669 for(unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL)
1671 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1672 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1673 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1675 // Issue loads for all pixel color components up-front:
1676 const unsigned int c0 = inPixelAddress[0];
1677 const unsigned int c1 = inPixelAddress[1];
1678 const unsigned int c2 = inPixelAddress[2];
1679 ///@ToDo: Optimise - Benchmark one 32bit load that will be unaligned 2/3 of the time + 3 rotate and masks, versus these three aligned byte loads, versus using an RGB packed, aligned(1) struct and letting compiler pick a strategy.
1681 // Output the pixel components:
1682 outScanline[outX] = static_cast<uint8_t>(c0);
1683 outScanline[outX + 1] = static_cast<uint8_t>(c1);
1684 outScanline[outX + 2] = static_cast<uint8_t>(c2);
1686 // Increment the fixed-point input coordinate:
1694 // Dispatch to a format-appropriate point sampling function:
1695 void PointSample(const unsigned char* inPixels,
1696 unsigned int inputWidth,
1697 unsigned int inputHeight,
1698 Pixel::Format pixelFormat,
1699 unsigned char* outPixels,
1700 unsigned int desiredWidth,
1701 unsigned int desiredHeight)
1703 // Check the pixel format is one that is supported:
1704 if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1706 if(pixelFormat == Pixel::RGB888)
1708 PointSample3BPP(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1710 else if(pixelFormat == Pixel::RGBA8888)
1712 PointSample4BPP(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1714 else if(pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88)
1716 PointSample2BPP(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1718 else if(pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1720 PointSample1BPP(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1724 DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
1729 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat));
1733 // Linear sampling group below
1737 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1738 inline uint8_t BilinearFilter1BPPByte(uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1740 return static_cast<uint8_t>(BilinearFilter1Component(tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical));
1743 /** @copydoc BilinearFilter1BPPByte */
1744 inline Pixel2Bytes BilinearFilter2Bytes(Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1747 pixel.l = static_cast<uint8_t>(BilinearFilter1Component(tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical));
1748 pixel.a = static_cast<uint8_t>(BilinearFilter1Component(tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical));
1752 /** @copydoc BilinearFilter1BPPByte */
1753 inline Pixel3Bytes BilinearFilterRGB888(Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1756 pixel.r = static_cast<uint8_t>(BilinearFilter1Component(tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical));
1757 pixel.g = static_cast<uint8_t>(BilinearFilter1Component(tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical));
1758 pixel.b = static_cast<uint8_t>(BilinearFilter1Component(tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical));
1762 /** @copydoc BilinearFilter1BPPByte */
1763 inline PixelRGB565 BilinearFilterRGB565(PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1765 const PixelRGB565 pixel = static_cast<PixelRGB565>((BilinearFilter1Component(tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical) << 11u) +
1766 (BilinearFilter1Component((tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical) << 5u) +
1767 BilinearFilter1Component(tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical));
1771 /** @copydoc BilinearFilter1BPPByte */
1772 inline Pixel4Bytes BilinearFilter4Bytes(Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1775 pixel.r = static_cast<uint8_t>(BilinearFilter1Component(tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical));
1776 pixel.g = static_cast<uint8_t>(BilinearFilter1Component(tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical));
1777 pixel.b = static_cast<uint8_t>(BilinearFilter1Component(tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical));
1778 pixel.a = static_cast<uint8_t>(BilinearFilter1Component(tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical));
1783 * @brief Generic version of bilinear sampling image resize function.
1784 * @note Limited to one compilation unit and exposed through type-specific
1785 * wrapper functions below.
1789 PIXEL (*BilinearFilter)(PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical),
1790 bool DEBUG_ASSERT_ALIGNMENT>
1791 inline void LinearSampleGeneric(const unsigned char* __restrict__ inPixels,
1792 ImageDimensions inputDimensions,
1793 unsigned char* __restrict__ outPixels,
1794 ImageDimensions desiredDimensions)
1796 const unsigned int inputWidth = inputDimensions.GetWidth();
1797 const unsigned int inputHeight = inputDimensions.GetHeight();
1798 const unsigned int desiredWidth = desiredDimensions.GetWidth();
1799 const unsigned int desiredHeight = desiredDimensions.GetHeight();
1801 DALI_ASSERT_DEBUG(((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
1802 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1803 "Input and output buffers cannot overlap.");
1804 if(DEBUG_ASSERT_ALIGNMENT)
1806 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, ...).");
1807 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, ...).");
1810 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1814 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1815 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1816 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1817 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1819 unsigned int inY = 0;
1820 for(unsigned int outY = 0; outY < desiredHeight; ++outY)
1822 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1824 // Find the two scanlines to blend and the weight to blend with:
1825 const unsigned int integerY1 = inY >> 16u;
1826 const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1827 const unsigned int inputYWeight = inY & 65535u;
1829 DALI_ASSERT_DEBUG(integerY1 < inputHeight);
1830 DALI_ASSERT_DEBUG(integerY2 < inputHeight);
1832 const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1833 const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1835 unsigned int inX = 0;
1836 for(unsigned int outX = 0; outX < desiredWidth; ++outX)
1838 // Work out the two pixel scanline offsets for this cluster of four samples:
1839 const unsigned int integerX1 = inX >> 16u;
1840 const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1842 // Execute the loads:
1843 const PIXEL pixel1 = inScanline1[integerX1];
1844 const PIXEL pixel2 = inScanline2[integerX1];
1845 const PIXEL pixel3 = inScanline1[integerX2];
1846 const PIXEL pixel4 = inScanline2[integerX2];
1847 ///@ToDo Optimise - for 1 and 2 and 4 byte types to execute a single 2, 4, or 8 byte load per pair (caveat clamping) and let half of them be unaligned.
1849 // Weighted bilinear filter:
1850 const unsigned int inputXWeight = inX & 65535u;
1851 outScanline[outX] = BilinearFilter(pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight);
1861 // Format-specific linear scaling instantiations:
1863 void LinearSample1BPP(const unsigned char* __restrict__ inPixels,
1864 ImageDimensions inputDimensions,
1865 unsigned char* __restrict__ outPixels,
1866 ImageDimensions desiredDimensions)
1868 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>(inPixels, inputDimensions, outPixels, desiredDimensions);
1871 void LinearSample2BPP(const unsigned char* __restrict__ inPixels,
1872 ImageDimensions inputDimensions,
1873 unsigned char* __restrict__ outPixels,
1874 ImageDimensions desiredDimensions)
1876 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>(inPixels, inputDimensions, outPixels, desiredDimensions);
1879 void LinearSampleRGB565(const unsigned char* __restrict__ inPixels,
1880 ImageDimensions inputDimensions,
1881 unsigned char* __restrict__ outPixels,
1882 ImageDimensions desiredDimensions)
1884 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>(inPixels, inputDimensions, outPixels, desiredDimensions);
1887 void LinearSample3BPP(const unsigned char* __restrict__ inPixels,
1888 ImageDimensions inputDimensions,
1889 unsigned char* __restrict__ outPixels,
1890 ImageDimensions desiredDimensions)
1892 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>(inPixels, inputDimensions, outPixels, desiredDimensions);
1895 void LinearSample4BPP(const unsigned char* __restrict__ inPixels,
1896 ImageDimensions inputDimensions,
1897 unsigned char* __restrict__ outPixels,
1898 ImageDimensions desiredDimensions)
1900 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>(inPixels, inputDimensions, outPixels, desiredDimensions);
1903 void Resample(const unsigned char* __restrict__ inPixels,
1904 ImageDimensions inputDimensions,
1905 unsigned char* __restrict__ outPixels,
1906 ImageDimensions desiredDimensions,
1907 Resampler::Filter filterType,
1911 // Got from the test.cpp of the ImageResampler lib.
1912 const float ONE_DIV_255 = 1.0f / 255.0f;
1913 const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
1914 const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
1915 const int ALPHA_CHANNEL = hasAlpha ? (numChannels - 1) : 0;
1917 static bool loadColorSpaces = true;
1918 static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
1919 static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
1921 if(loadColorSpaces) // Only create the color space conversions on the first execution
1923 loadColorSpaces = false;
1925 for(int i = 0; i <= MAX_UNSIGNED_CHAR; ++i)
1927 srgbToLinear[i] = pow(static_cast<float>(i) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA);
1930 const float invLinearToSrgbTableSize = 1.0f / static_cast<float>(LINEAR_TO_SRGB_TABLE_SIZE);
1931 const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
1933 for(int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i)
1935 int k = static_cast<int>(255.0f * pow(static_cast<float>(i) * invLinearToSrgbTableSize, invSourceGamma) + 0.5f);
1940 else if(k > MAX_UNSIGNED_CHAR)
1942 k = MAX_UNSIGNED_CHAR;
1944 linearToSrgb[i] = static_cast<unsigned char>(k);
1948 std::vector<Resampler*> resamplers(numChannels);
1949 std::vector<Vector<float>> samples(numChannels);
1951 const int srcWidth = inputDimensions.GetWidth();
1952 const int srcHeight = inputDimensions.GetHeight();
1953 const int dstWidth = desiredDimensions.GetWidth();
1954 const int dstHeight = desiredDimensions.GetHeight();
1956 // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
1957 // used for the other components (a memory and slight cache efficiency optimization).
1958 resamplers[0] = new Resampler(srcWidth,
1962 Resampler::BOUNDARY_CLAMP,
1963 0.0f, // sample_low,
1964 1.0f, // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
1965 filterType, // The type of filter.
1967 NULL, // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
1968 FILTER_SCALE, // src_x_ofs,
1969 FILTER_SCALE); // src_y_ofs. Offset input image by specified amount (fractional values okay).
1970 samples[0].Resize(srcWidth);
1971 for(int i = 1; i < numChannels; ++i)
1973 resamplers[i] = new Resampler(srcWidth,
1977 Resampler::BOUNDARY_CLAMP,
1981 resamplers[0]->get_clist_x(),
1982 resamplers[0]->get_clist_y(),
1985 samples[i].Resize(srcWidth);
1988 const int srcPitch = srcWidth * numChannels;
1989 const int dstPitch = dstWidth * numChannels;
1992 for(int srcY = 0; srcY < srcHeight; ++srcY)
1994 const unsigned char* pSrc = &inPixels[srcY * srcPitch];
1996 for(int x = 0; x < srcWidth; ++x)
1998 for(int c = 0; c < numChannels; ++c)
2000 if(c == ALPHA_CHANNEL && hasAlpha)
2002 samples[c][x] = *pSrc++ * ONE_DIV_255;
2006 samples[c][x] = srgbToLinear[*pSrc++];
2011 for(int c = 0; c < numChannels; ++c)
2013 if(!resamplers[c]->put_line(&samples[c][0]))
2015 DALI_ASSERT_DEBUG(!"Out of memory");
2022 for(compIndex = 0; compIndex < numChannels; ++compIndex)
2024 const float* pOutputSamples = resamplers[compIndex]->get_line();
2030 const bool isAlphaChannel = (compIndex == ALPHA_CHANNEL && hasAlpha);
2031 DALI_ASSERT_DEBUG(dstY < dstHeight);
2032 unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
2034 for(int x = 0; x < dstWidth; ++x)
2038 int c = static_cast<int>(255.0f * pOutputSamples[x] + 0.5f);
2043 else if(c > MAX_UNSIGNED_CHAR)
2045 c = MAX_UNSIGNED_CHAR;
2047 *pDst = static_cast<unsigned char>(c);
2051 int j = static_cast<int>(LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f);
2056 else if(j >= LINEAR_TO_SRGB_TABLE_SIZE)
2058 j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
2060 *pDst = linearToSrgb[j];
2063 pDst += numChannels;
2066 if(compIndex < numChannels)
2075 // Delete the resamplers.
2076 for(int i = 0; i < numChannels; ++i)
2078 delete resamplers[i];
2082 void LanczosSample4BPP(const unsigned char* __restrict__ inPixels,
2083 ImageDimensions inputDimensions,
2084 unsigned char* __restrict__ outPixels,
2085 ImageDimensions desiredDimensions)
2087 Resample(inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true);
2090 void LanczosSample1BPP(const unsigned char* __restrict__ inPixels,
2091 ImageDimensions inputDimensions,
2092 unsigned char* __restrict__ outPixels,
2093 ImageDimensions desiredDimensions)
2096 Resample(inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false);
2099 // Dispatch to a format-appropriate linear sampling function:
2100 void LinearSample(const unsigned char* __restrict__ inPixels,
2101 ImageDimensions inDimensions,
2102 Pixel::Format pixelFormat,
2103 unsigned char* __restrict__ outPixels,
2104 ImageDimensions outDimensions)
2106 // Check the pixel format is one that is supported:
2107 if(pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565)
2109 if(pixelFormat == Pixel::RGB888)
2111 LinearSample3BPP(inPixels, inDimensions, outPixels, outDimensions);
2113 else if(pixelFormat == Pixel::RGBA8888)
2115 LinearSample4BPP(inPixels, inDimensions, outPixels, outDimensions);
2117 else if(pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
2119 LinearSample1BPP(inPixels, inDimensions, outPixels, outDimensions);
2121 else if(pixelFormat == Pixel::LA88)
2123 LinearSample2BPP(inPixels, inDimensions, outPixels, outDimensions);
2125 else if(pixelFormat == Pixel::RGB565)
2127 LinearSampleRGB565(inPixels, inDimensions, outPixels, outDimensions);
2131 DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
2136 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat));
2140 void RotateByShear(const uint8_t* const pixelsIn,
2141 unsigned int widthIn,
2142 unsigned int heightIn,
2143 unsigned int pixelSize,
2145 uint8_t*& pixelsOut,
2146 unsigned int& widthOut,
2147 unsigned int& heightOut)
2149 // @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
2151 // Do first the fast rotations to transform the angle into a (-45..45] range.
2153 float fastRotationPerformed = false;
2154 if((radians > Math::PI_4) && (radians <= RAD_135))
2156 // Angle in (45.0 .. 135.0]
2157 // Rotate image by 90 degrees into temporary image,
2158 // so it requires only an extra rotation angle
2159 // of -45.0 .. +45.0 to complete rotation.
2160 fastRotationPerformed = Rotate90(pixelsIn,
2168 if(!fastRotationPerformed)
2170 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2171 // The fast rotation failed.
2175 radians -= Math::PI_2;
2177 else if((radians > RAD_135) && (radians <= RAD_225))
2179 // Angle in (135.0 .. 225.0]
2180 // Rotate image by 180 degrees into temporary image,
2181 // so it requires only an extra rotation angle
2182 // of -45.0 .. +45.0 to complete rotation.
2184 fastRotationPerformed = Rotate180(pixelsIn,
2190 if(!fastRotationPerformed)
2192 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2193 // The fast rotation failed.
2197 radians -= Math::PI;
2199 heightOut = heightIn;
2201 else if((radians > RAD_225) && (radians <= RAD_315))
2203 // Angle in (225.0 .. 315.0]
2204 // Rotate image by 270 degrees into temporary image,
2205 // so it requires only an extra rotation angle
2206 // of -45.0 .. +45.0 to complete rotation.
2208 fastRotationPerformed = Rotate270(pixelsIn,
2216 if(!fastRotationPerformed)
2218 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2219 // The fast rotation failed.
2226 if(fabs(radians) < Dali::Math::MACHINE_EPSILON_10)
2228 // Nothing else to do if the angle is zero.
2229 // The rotation angle was 90, 180 or 270.
2231 // @note Allocated memory by 'Fast Rotations', if any, has to be freed by the called to this function.
2235 const uint8_t* const firstHorizontalSkewPixelsIn = fastRotationPerformed ? pixelsOut : pixelsIn;
2236 std::unique_ptr<uint8_t, void (*)(void*)> tmpPixelsInPtr((fastRotationPerformed ? pixelsOut : nullptr), free);
2238 // Reset the input/output
2240 heightIn = heightOut;
2241 pixelsOut = nullptr;
2243 const float angleSinus = sin(radians);
2244 const float angleCosinus = cos(radians);
2245 const float angleTangent = tan(0.5f * radians);
2247 ///////////////////////////////////////
2248 // Perform 1st shear (horizontal)
2249 ///////////////////////////////////////
2251 // Calculate first shear (horizontal) destination image dimensions
2253 widthOut = widthIn + static_cast<unsigned int>(fabs(angleTangent) * static_cast<float>(heightIn));
2254 heightOut = heightIn;
2256 // Allocate the buffer for the 1st shear
2257 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2259 if(nullptr == pixelsOut)
2264 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2266 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Fast rotations'.
2267 // Nothing else to do if the memory allocation fails.
2271 for(unsigned int y = 0u; y < heightOut; ++y)
2273 const float shear = angleTangent * ((angleTangent >= 0.f) ? (0.5f + static_cast<float>(y)) : (0.5f + static_cast<float>(y) - static_cast<float>(heightOut)));
2275 const int intShear = static_cast<int>(floor(shear));
2276 HorizontalSkew(firstHorizontalSkewPixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>(intShear));
2279 // Reset the 'pixel in' pointer with the output of the 'First Horizontal Skew' and free the memory allocated by the 'Fast Rotations'.
2280 tmpPixelsInPtr.reset(pixelsOut);
2281 unsigned int tmpWidthIn = widthOut;
2282 unsigned int tmpHeightIn = heightOut;
2284 // Reset the input/output
2285 pixelsOut = nullptr;
2287 ///////////////////////////////////////
2288 // Perform 2nd shear (vertical)
2289 ///////////////////////////////////////
2291 // Calc 2nd shear (vertical) destination image dimensions
2292 heightOut = static_cast<unsigned int>(static_cast<float>(widthIn) * fabs(angleSinus) + static_cast<float>(heightIn) * angleCosinus);
2294 // Allocate the buffer for the 2nd shear
2295 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2297 if(nullptr == pixelsOut)
2302 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2303 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'First Horizontal Skew'.
2304 // Nothing else to do if the memory allocation fails.
2308 // Variable skew offset
2309 float offset = angleSinus * ((angleSinus > 0.f) ? static_cast<float>(widthIn - 1u) : -(static_cast<float>(widthIn) - static_cast<float>(widthOut)));
2311 unsigned int column = 0u;
2312 for(column = 0u; column < widthOut; ++column, offset -= angleSinus)
2314 const int shear = static_cast<int>(floor(offset));
2315 VerticalSkew(tmpPixelsInPtr.get(), tmpWidthIn, tmpHeightIn, pixelSize, pixelsOut, widthOut, heightOut, column, shear, offset - static_cast<float>(shear));
2317 // Reset the 'pixel in' pointer with the output of the 'Vertical Skew' and free the memory allocated by the 'First Horizontal Skew'.
2318 // Reset the input/output
2319 tmpPixelsInPtr.reset(pixelsOut);
2320 tmpWidthIn = widthOut;
2321 tmpHeightIn = heightOut;
2322 pixelsOut = nullptr;
2324 ///////////////////////////////////////
2325 // Perform 3rd shear (horizontal)
2326 ///////////////////////////////////////
2328 // Calc 3rd shear (horizontal) destination image dimensions
2329 widthOut = static_cast<unsigned int>(static_cast<float>(heightIn) * fabs(angleSinus) + static_cast<float>(widthIn) * angleCosinus) + 1u;
2331 // Allocate the buffer for the 3rd shear
2332 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2334 if(nullptr == pixelsOut)
2339 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2340 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2341 // Nothing else to do if the memory allocation fails.
2345 offset = (angleSinus >= 0.f) ? -angleSinus * angleTangent * static_cast<float>(widthIn - 1u) : angleTangent * (static_cast<float>(widthIn - 1u) * -angleSinus + (1.f - static_cast<float>(heightOut)));
2347 for(unsigned int y = 0u; y < heightOut; ++y, offset += angleTangent)
2349 const int shear = static_cast<int>(floor(offset));
2350 HorizontalSkew(tmpPixelsInPtr.get(), tmpWidthIn, pixelSize, pixelsOut, widthOut, y, shear, offset - static_cast<float>(shear));
2353 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2354 // @note Allocated memory by the last 'Horizontal Skew' has to be freed by the caller to this function.
2357 void HorizontalShear(const uint8_t* const pixelsIn,
2358 unsigned int widthIn,
2359 unsigned int heightIn,
2360 unsigned int pixelSize,
2362 uint8_t*& pixelsOut,
2363 unsigned int& widthOut,
2364 unsigned int& heightOut)
2366 // Calculate the destination image dimensions.
2368 const float absRadians = fabs(radians);
2370 if(absRadians > Math::PI_4)
2372 // Can't shear more than 45 degrees.
2376 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Can't shear more than 45 degrees (PI/4 radians). radians : %f\n", radians);
2380 widthOut = widthIn + static_cast<unsigned int>(ceil(absRadians * static_cast<float>(heightIn)));
2381 heightOut = heightIn;
2383 // Allocate the buffer for the shear.
2384 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2386 if(nullptr == pixelsOut)
2391 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2395 for(unsigned int y = 0u; y < heightOut; ++y)
2397 const float shear = radians * ((radians >= 0.f) ? (0.5f + static_cast<float>(y)) : (0.5f + static_cast<float>(y) - static_cast<float>(heightOut)));
2399 const int intShear = static_cast<int>(floor(shear));
2400 HorizontalSkew(pixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>(intShear));
2404 } /* namespace Platform */
2405 } /* namespace Internal */
2406 } /* namespace Dali */