2 * Copyright (c) 2022 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 uint8_t 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] strideIn The stride of the input buffer.
509 * @param[in] pixelSize The size of the pixel.
510 * @param[out] pixelsOut The rotated output buffer.
511 * @param[out] widthOut The width of the output buffer.
512 * @param[out] heightOut The height of the output buffer.
514 * @return Whether the rotation succeeded.
516 bool Rotate90(const uint8_t* const pixelsIn,
517 unsigned int widthIn,
518 unsigned int heightIn,
519 unsigned int strideIn,
520 unsigned int pixelSize,
522 unsigned int& widthOut,
523 unsigned int& heightOut)
525 // The new size of the image.
529 // Allocate memory for the rotated buffer.
530 // Output buffer is tightly packed
531 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
532 if(nullptr == pixelsOut)
537 // Return if the memory allocations fails.
541 // Rotate the buffer.
542 for(unsigned int y = 0u; y < heightIn; ++y)
544 const unsigned int srcLineIndex = y * strideIn;
545 const unsigned int dstX = y;
546 for(unsigned int x = 0u; x < widthIn; ++x)
548 const unsigned int dstY = heightOut - x - 1u;
549 const unsigned int dstIndex = pixelSize * (dstY * widthOut + dstX);
550 const unsigned int srcIndex = pixelSize * (srcLineIndex + x);
552 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
554 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
563 * @brief Rotates the given buffer @p pixelsIn 180 degrees counter clockwise.
565 * @note It allocates memory for the returned @p pixelsOut buffer.
566 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
567 * @note It may fail if malloc() fails to allocate memory.
569 * @param[in] pixelsIn The input buffer.
570 * @param[in] widthIn The width of the input buffer.
571 * @param[in] heightIn The height of the input buffer.
572 * @param[in] strideIn The stride of the input buffer.
573 * @param[in] pixelSize The size of the pixel.
574 * @param[out] pixelsOut The rotated output buffer.
576 * @return Whether the rotation succeeded.
578 bool Rotate180(const uint8_t* const pixelsIn,
579 unsigned int widthIn,
580 unsigned int heightIn,
581 unsigned int strideIn,
582 unsigned int pixelSize,
585 // Allocate memory for the rotated buffer.
586 // Output buffer is tightly packed
587 pixelsOut = static_cast<uint8_t*>(malloc(widthIn * heightIn * pixelSize));
588 if(nullptr == pixelsOut)
590 // Return if the memory allocations fails.
594 // Rotate the buffer.
595 for(unsigned int y = 0u; y < heightIn; ++y)
597 const unsigned int srcLineIndex = y * strideIn;
598 const unsigned int dstY = heightIn - y - 1u;
599 for(unsigned int x = 0u; x < widthIn; ++x)
601 const unsigned int dstX = widthIn - x - 1u;
602 const unsigned int dstIndex = pixelSize * (dstY * widthIn + dstX);
603 const unsigned int srcIndex = pixelSize * (srcLineIndex + x);
605 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
607 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
616 * @brief Rotates the given buffer @p pixelsIn 270 degrees counter clockwise.
618 * @note It allocates memory for the returned @p pixelsOut buffer.
619 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
620 * @note It may fail if malloc() fails to allocate memory.
622 * @param[in] pixelsIn The input buffer.
623 * @param[in] widthIn The width of the input buffer.
624 * @param[in] heightIn The height of the input buffer.
625 * @param[in] strideIn The stride of the input buffer.
626 * @param[in] pixelSize The size of the pixel.
627 * @param[out] pixelsOut The rotated output buffer.
628 * @param[out] widthOut The width of the output buffer.
629 * @param[out] heightOut The height of the output buffer.
631 * @return Whether the rotation succeeded.
633 bool Rotate270(const uint8_t* const pixelsIn,
634 unsigned int widthIn,
635 unsigned int heightIn,
636 unsigned int strideIn,
637 unsigned int pixelSize,
639 unsigned int& widthOut,
640 unsigned int& heightOut)
642 // The new size of the image.
646 // Allocate memory for the rotated buffer.
647 // Output buffer is tightly packed
648 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
649 if(nullptr == pixelsOut)
654 // Return if the memory allocations fails.
658 // Rotate the buffer.
659 for(unsigned int y = 0u; y < heightIn; ++y)
661 const unsigned int srcLineIndex = y * strideIn;
662 const unsigned int dstX = widthOut - y - 1u;
663 for(unsigned int x = 0u; x < widthIn; ++x)
665 const unsigned int dstY = x;
666 const unsigned int dstIndex = pixelSize * (dstY * widthOut + dstX);
667 const unsigned int srcIndex = pixelSize * (srcLineIndex + x);
669 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
671 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
680 * @brief Skews a row horizontally (with filtered weights)
682 * @note Limited to 45 degree skewing only.
683 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
685 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
686 * @param[in] srcWidth The width of the input pixel buffer.
687 * @param[in] srcStride The stride of the input pixel buffer.
688 * @param[in] pixelSize The size of the pixel.
689 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
690 * @param[in] dstWidth The width of the output pixel buffer.
691 * @param[in] row The row index.
692 * @param[in] offset The skew offset.
693 * @param[in] weight The relative weight of right pixel.
695 void HorizontalSkew(const uint8_t* const srcBufferPtr,
698 unsigned int pixelSize,
699 uint8_t*& dstBufferPtr,
707 // Fill gap left of skew with background.
708 memset(dstBufferPtr + row * pixelSize * dstWidth, 0u, pixelSize * offset);
711 unsigned char oldLeft[4u] = {0u, 0u, 0u, 0u};
714 for(i = 0u; i < srcWidth; ++i)
716 // Loop through row pixels
717 const unsigned int srcIndex = pixelSize * (row * srcStride + i);
719 unsigned char src[4u] = {0u, 0u, 0u, 0u};
720 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
722 src[channel] = *(srcBufferPtr + srcIndex + channel);
726 unsigned char left[4u] = {0u, 0u, 0u, 0u};
727 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
729 left[channel] = static_cast<unsigned char>(static_cast<float>(src[channel]) * weight);
731 // Update left over on source
732 src[channel] -= (left[channel] - oldLeft[channel]);
736 if((i + offset >= 0) && (i + offset < dstWidth))
738 const unsigned int dstIndex = pixelSize * (row * dstWidth + i + offset);
740 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
742 *(dstBufferPtr + dstIndex + channel) = src[channel];
746 // Save leftover for next pixel in scan
747 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
749 oldLeft[channel] = left[channel];
753 // Go to rightmost point of skew
757 // If still in image bounds, put leftovers there
758 const unsigned int dstIndex = pixelSize * (row * dstWidth + i);
760 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
762 *(dstBufferPtr + dstIndex + channel) = oldLeft[channel];
765 // Clear to the right of the skewed line with background
767 memset(dstBufferPtr + pixelSize * (row * dstWidth + i), 0u, pixelSize * (dstWidth - i));
772 * @brief Skews a column vertically (with filtered weights)
774 * @note Limited to 45 degree skewing only.
775 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
777 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
778 * @param[in] srcWidth The width of the input pixel buffer.
779 * @param[in] srcHeight The height of the input pixel buffer.
780 * @param[in] srcStride The stride of the input pixel buffer.
781 * @param[in] pixelSize The size of the pixel.
782 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
783 * @param[in] dstWidth The width of the output pixel buffer.
784 * @param[in] dstHeight The height of the output pixel buffer.
785 * @param[in] column The column index.
786 * @param[in] offset The skew offset.
787 * @param[in] weight The relative weight of uppeer pixel.
789 void VerticalSkew(const uint8_t* const srcBufferPtr,
793 unsigned int pixelSize,
794 uint8_t*& dstBufferPtr,
801 for(int i = 0; i < offset; ++i)
803 // Fill gap above skew with background
804 const unsigned int dstIndex = pixelSize * (i * dstWidth + column);
806 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
808 *(dstBufferPtr + dstIndex + channel) = 0u;
812 unsigned char oldLeft[4u] = {0u, 0u, 0u, 0u};
816 for(i = 0; i < srcHeight; ++i)
818 // Loop through column pixels
819 const unsigned int srcIndex = pixelSize * (i * srcStride + column);
821 unsigned char src[4u] = {0u, 0u, 0u, 0u};
822 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
824 src[channel] = *(srcBufferPtr + srcIndex + channel);
830 unsigned char left[4u] = {0u, 0u, 0u, 0u};
831 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
833 left[channel] = static_cast<unsigned char>(static_cast<float>(src[channel]) * weight);
834 // Update left over on source
835 src[channel] -= (left[channel] - oldLeft[channel]);
839 if((yPos >= 0) && (yPos < dstHeight))
841 const unsigned int dstIndex = pixelSize * (yPos * dstWidth + column);
843 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
845 *(dstBufferPtr + dstIndex + channel) = src[channel];
849 // Save leftover for next pixel in scan
850 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
852 oldLeft[channel] = left[channel];
856 // Go to bottom point of skew
860 // If still in image bounds, put leftovers there
861 const unsigned int dstIndex = pixelSize * (i * dstWidth + column);
863 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
865 *(dstBufferPtr + dstIndex + channel) = oldLeft[channel];
869 while(++i < dstHeight)
871 // Clear below skewed line with background
872 const unsigned int dstIndex = pixelSize * (i * dstWidth + column);
874 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
876 *(dstBufferPtr + dstIndex + channel) = 0u;
883 ImageDimensions CalculateDesiredDimensions(ImageDimensions rawDimensions, ImageDimensions requestedDimensions)
885 return CalculateDesiredDimensions(rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight());
889 * @brief Apply cropping and padding for specified fitting mode.
891 * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
892 * based on the fitting mode.
894 * This will add vertical or horizontal borders if necessary.
895 * Crop the source image data vertically or horizontally if necessary.
896 * The aspect of the source image is preserved.
897 * If the source image is smaller than the desired size, the algorithm will modify the the newly created
898 * bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
899 * GPU scaling to be performed at render time giving the same result with less texture traversal.
901 * @param[in] bitmap The source pixel buffer to perform modifications on.
902 * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
903 * @param[in] fittingMode The fitting mode to use.
905 * @return A new bitmap with the padding and cropping required for fitting mode applied.
906 * If no modification is needed or possible, the passed in bitmap is returned.
908 Dali::Devel::PixelBuffer CropAndPadForFittingMode(Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode);
911 * @brief Adds horizontal or vertical borders to the source image.
913 * @param[in] targetPixels The destination image pointer to draw the borders on.
914 * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer.
915 * @param[in] targetDimensions The dimensions of the destination image.
916 * @param[in] padDimensions The columns and scanlines to pad with borders.
918 void AddBorders(PixelBuffer* targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions);
920 Dali::Devel::PixelBuffer ApplyAttributesToBitmap(Dali::Devel::PixelBuffer bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode)
924 // Calculate the desired box, accounting for a possible zero component:
925 const ImageDimensions desiredDimensions = CalculateDesiredDimensions(bitmap.GetWidth(), bitmap.GetHeight(), dimensions.GetWidth(), dimensions.GetHeight());
927 // If a different size than the raw one has been requested, resize the image
928 // maximally using a repeated box filter without making it smaller than the
929 // requested size in either dimension:
930 bitmap = DownscaleBitmap(bitmap, desiredDimensions, fittingMode, samplingMode);
932 // Cut the bitmap according to the desired width and height so that the
933 // resulting bitmap has the same aspect ratio as the desired dimensions.
934 // Add crop and add borders if necessary depending on fitting mode.
937 bitmap = CropAndPadForFittingMode(bitmap, desiredDimensions, fittingMode);
944 Dali::Devel::PixelBuffer CropAndPadForFittingMode(Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode)
946 const unsigned int inputWidth = bitmap.GetWidth();
947 const unsigned int inputHeight = bitmap.GetHeight();
948 const unsigned int inputStride = bitmap.GetStride();
950 if(desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u)
952 DALI_LOG_WARNING("Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight());
954 else if(inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight())
956 // Calculate any padding or cropping that needs to be done based on the fitting mode.
957 // Note: If the desired size is larger than the original image, the desired size will be
958 // reduced while maintaining the aspect, in order to save unnecessary memory usage.
959 int scanlinesToCrop = 0;
960 int columnsToCrop = 0;
962 CalculateBordersFromFittingMode(ImageDimensions(inputWidth, inputHeight), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop);
964 unsigned int desiredWidth(desiredDimensions.GetWidth());
965 unsigned int desiredHeight(desiredDimensions.GetHeight());
967 // Action the changes by making a new bitmap with the central part of the loaded one if required.
968 if(scanlinesToCrop != 0 || columnsToCrop != 0)
970 // Split the adding and removing of scanlines and columns into separate variables,
971 // so we can use one piece of generic code to action the changes.
972 unsigned int scanlinesToPad = 0;
973 unsigned int columnsToPad = 0;
974 if(scanlinesToCrop < 0)
976 scanlinesToPad = -scanlinesToCrop;
979 if(columnsToCrop < 0)
981 columnsToPad = -columnsToCrop;
985 // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
986 if((desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE) || (desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE) ||
987 (columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE) || (scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE))
989 DALI_LOG_WARNING("Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight);
993 // Create new PixelBuffer with the desired size.
994 const auto pixelFormat = bitmap.GetPixelFormat();
996 auto croppedBitmap = Devel::PixelBuffer::New(desiredWidth, desiredHeight, pixelFormat);
998 // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
999 // The cropping is added to the source pointer, and the padding is added to the destination.
1000 const auto bytesPerPixel = Pixel::GetBytesPerPixel(pixelFormat);
1001 const PixelBuffer* const sourcePixels = bitmap.GetBuffer() + ((((scanlinesToCrop / 2) * inputStride) + (columnsToCrop / 2)) * bytesPerPixel);
1002 PixelBuffer* const targetPixels = croppedBitmap.GetBuffer();
1003 PixelBuffer* const targetPixelsActive = targetPixels + ((((scanlinesToPad / 2) * desiredWidth) + (columnsToPad / 2)) * bytesPerPixel);
1004 DALI_ASSERT_DEBUG(sourcePixels && targetPixels);
1006 // Copy the image data to the new bitmap.
1007 // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
1008 unsigned int outputSpan(desiredWidth * bytesPerPixel);
1009 if(columnsToCrop == 0 && columnsToPad == 0 && inputStride == inputWidth)
1011 memcpy(targetPixelsActive, sourcePixels, (desiredHeight - scanlinesToPad) * outputSpan);
1015 // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
1016 // Precalculate any constants to optimize the inner loop.
1017 const unsigned int inputSpan(inputStride * bytesPerPixel);
1018 const unsigned int copySpan((desiredWidth - columnsToPad) * bytesPerPixel);
1019 const unsigned int scanlinesToCopy(desiredHeight - scanlinesToPad);
1021 for(unsigned int y = 0; y < scanlinesToCopy; ++y)
1023 memcpy(&targetPixelsActive[y * outputSpan], &sourcePixels[y * inputSpan], copySpan);
1027 // Add vertical or horizontal borders to the final image (if required).
1028 desiredDimensions.SetWidth(desiredWidth);
1029 desiredDimensions.SetHeight(desiredHeight);
1030 AddBorders(croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions(columnsToPad, scanlinesToPad));
1031 // Overwrite the loaded bitmap with the cropped version
1032 bitmap = croppedBitmap;
1039 void AddBorders(PixelBuffer* targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions)
1041 // Assign ints for faster access.
1042 unsigned int desiredWidth(targetDimensions.GetWidth());
1043 unsigned int desiredHeight(targetDimensions.GetHeight());
1044 unsigned int columnsToPad(padDimensions.GetWidth());
1045 unsigned int scanlinesToPad(padDimensions.GetHeight());
1046 unsigned int outputSpan(desiredWidth * bytesPerPixel);
1048 // Add letterboxing (symmetrical borders) if needed.
1049 if(scanlinesToPad > 0)
1051 // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
1052 memset(targetPixels, BORDER_FILL_VALUE, (scanlinesToPad / 2) * outputSpan);
1054 // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
1055 // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
1056 unsigned int bottomBorderHeight = scanlinesToPad - (scanlinesToPad / 2);
1059 memset(&targetPixels[(desiredHeight - bottomBorderHeight) * outputSpan], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan);
1061 else if(columnsToPad > 0)
1063 // Add a left and right border.
1065 // Pre-calculate span size outside of loop.
1066 unsigned int leftBorderSpanWidth((columnsToPad / 2) * bytesPerPixel);
1067 for(unsigned int y = 0; y < desiredHeight; ++y)
1069 memset(&targetPixels[y * outputSpan], BORDER_FILL_VALUE, leftBorderSpanWidth);
1073 // Pre-calculate the initial x offset as it is always the same for a small optimization.
1074 // We subtract columnsToPad/2 from columnsToPad so that we have the correct
1075 // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
1076 unsigned int rightBorderWidth = columnsToPad - (columnsToPad / 2);
1077 PixelBuffer* const destPixelsRightBorder(targetPixels + ((desiredWidth - rightBorderWidth) * bytesPerPixel));
1078 unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
1080 for(unsigned int y = 0; y < desiredHeight; ++y)
1082 memset(&destPixelsRightBorder[y * outputSpan], BORDER_FILL_VALUE, rightBorderSpanWidth);
1087 Dali::Devel::PixelBuffer DownscaleBitmap(Dali::Devel::PixelBuffer bitmap,
1088 ImageDimensions desired,
1089 FittingMode::Type fittingMode,
1090 SamplingMode::Type samplingMode)
1092 // Source dimensions as loaded from resources (e.g. filesystem):
1093 auto bitmapWidth = bitmap.GetWidth();
1094 auto bitmapHeight = bitmap.GetHeight();
1095 auto bitmapStride = bitmap.GetStride();
1096 // Desired dimensions (the rectangle to fit the source image to):
1097 auto desiredWidth = desired.GetWidth();
1098 auto desiredHeight = desired.GetHeight();
1100 Dali::Devel::PixelBuffer outputBitmap{bitmap};
1102 // If a different size than the raw one has been requested, resize the image:
1104 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
1105 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)))
1107 auto pixelFormat = bitmap.GetPixelFormat();
1109 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
1110 unsigned int shrunkWidth = -1, shrunkHeight = -1, outStride = -1;
1111 DownscaleInPlacePow2(bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, bitmapStride, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight, outStride);
1113 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
1114 const ImageDimensions filteredDimensions = FitToScalingMode(ImageDimensions(desiredWidth, desiredHeight), ImageDimensions(shrunkWidth, shrunkHeight), fittingMode);
1115 const unsigned int filteredWidth = filteredDimensions.GetWidth();
1116 const unsigned int filteredHeight = filteredDimensions.GetHeight();
1118 // Run a filter to scale down the bitmap if it needs it:
1119 bool filtered = false;
1120 if(filteredWidth < shrunkWidth || filteredHeight < shrunkHeight)
1122 if(samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
1123 samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST)
1125 outputBitmap = Dali::Devel::PixelBuffer::New(filteredWidth, filteredHeight, pixelFormat);
1129 if(samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR)
1131 LinearSample(bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), outStride, pixelFormat, outputBitmap.GetBuffer(), filteredDimensions);
1135 PointSample(bitmap.GetBuffer(), shrunkWidth, shrunkHeight, outStride, pixelFormat, outputBitmap.GetBuffer(), filteredWidth, filteredHeight);
1141 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
1142 if(filtered == false && (shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight))
1144 // The buffer is downscaled and it is tightly packed. We don't need to set a stride.
1145 outputBitmap = MakePixelBuffer(bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight);
1149 return outputBitmap;
1155 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
1156 * @param test Which combination of the two dimensions matter for terminating the filtering.
1157 * @param scaledWidth The width of the current downscaled image.
1158 * @param scaledHeight The height of the current downscaled image.
1159 * @param desiredWidth The target width for the downscaling.
1160 * @param desiredHeight The target height for the downscaling.
1162 bool ContinueScaling(BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight)
1164 bool keepScaling = false;
1165 const unsigned int nextWidth = scaledWidth >> 1u;
1166 const unsigned int nextHeight = scaledHeight >> 1u;
1168 if(nextWidth >= 1u && nextHeight >= 1u)
1172 case BoxDimensionTestEither:
1174 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
1177 case BoxDimensionTestBoth:
1179 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
1182 case BoxDimensionTestX:
1184 keepScaling = nextWidth >= desiredWidth;
1187 case BoxDimensionTestY:
1189 keepScaling = nextHeight >= desiredHeight;
1199 * @brief A shared implementation of the overall iterative box filter
1200 * downscaling algorithm.
1202 * Specialise this for particular pixel formats by supplying the number of bytes
1203 * per pixel and two functions: one for averaging pairs of neighbouring pixels
1204 * on a single scanline, and a second for averaging pixels at corresponding
1205 * positions on different scanlines.
1208 int BYTES_PER_PIXEL,
1209 void (*HalveScanlineInPlace)(unsigned char* const pixels, const unsigned int width),
1210 void (*AverageScanlines)(const unsigned char* const scanline1, const unsigned char* const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width)>
1211 void DownscaleInPlacePow2Generic(unsigned char* const pixels,
1212 const unsigned int inputWidth,
1213 const unsigned int inputHeight,
1214 const unsigned int inputStride,
1215 const unsigned int desiredWidth,
1216 const unsigned int desiredHeight,
1217 BoxDimensionTest dimensionTest,
1219 unsigned& outHeight,
1220 unsigned& outStride)
1226 ValidateScalingParameters(inputWidth, inputHeight, desiredWidth, desiredHeight);
1228 // Scale the image until it would be smaller than desired, stopping if the
1229 // resulting height or width would be less than 1:
1230 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight, stride = inputStride;
1231 while(ContinueScaling(dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight))
1233 const unsigned int lastWidth = scaledWidth;
1234 const unsigned int lastStride = stride;
1236 scaledHeight >>= 1u;
1237 stride = scaledWidth;
1239 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight);
1241 const unsigned int lastScanlinePair = scaledHeight - 1;
1243 // Scale pairs of scanlines until any spare one at the end is dropped:
1244 for(unsigned int y = 0; y <= lastScanlinePair; ++y)
1246 // Scale two scanlines horizontally:
1247 HalveScanlineInPlace(&pixels[y * 2 * lastStride * BYTES_PER_PIXEL], lastWidth);
1248 HalveScanlineInPlace(&pixels[(y * 2 + 1) * lastStride * BYTES_PER_PIXEL], lastWidth);
1250 // Scale vertical pairs of pixels while the last two scanlines are still warm in
1251 // the CPU cache(s):
1252 // Note, better access patterns for cache-coherence are possible for very large
1253 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
1254 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
1256 &pixels[y * 2 * lastStride * BYTES_PER_PIXEL],
1257 &pixels[(y * 2 + 1) * lastStride * BYTES_PER_PIXEL],
1258 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
1263 ///@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.
1264 outWidth = scaledWidth;
1265 outHeight = scaledHeight;
1271 void HalveScanlineInPlaceRGB888(unsigned char* const pixels, const unsigned int width)
1273 DebugAssertScanlineParameters(pixels, width);
1275 const unsigned int lastPair = EvenDown(width - 2);
1279 * for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1281 * // Load all the byte pixel components we need:
1282 * const unsigned int c11 = pixels[pixel * 3];
1283 * const unsigned int c12 = pixels[pixel * 3 + 1];
1284 * const unsigned int c13 = pixels[pixel * 3 + 2];
1285 * const unsigned int c21 = pixels[pixel * 3 + 3];
1286 * const unsigned int c22 = pixels[pixel * 3 + 4];
1287 * const unsigned int c23 = pixels[pixel * 3 + 5];
1289 * // Save the averaged byte pixel components:
1290 * pixels[outPixel * 3] = static_cast<unsigned char>(AverageComponent(c11, c21));
1291 * pixels[outPixel * 3 + 1] = static_cast<unsigned char>(AverageComponent(c12, c22));
1292 * pixels[outPixel * 3 + 2] = static_cast<unsigned char>(AverageComponent(c13, c23));
1296 //@ToDo : Fix here if we found that collect 12 bytes == 3 uint32_t with 4 colors, and calculate in one-operation
1297 std::uint8_t* inPixelPtr = pixels;
1298 std::uint8_t* outPixelPtr = pixels;
1299 for(std::uint32_t scanedPixelCount = 0; scanedPixelCount <= lastPair; scanedPixelCount += 2)
1301 *(outPixelPtr + 0) = ((*(inPixelPtr + 0) ^ *(inPixelPtr + 3)) >> 1) + (*(inPixelPtr + 0) & *(inPixelPtr + 3));
1302 *(outPixelPtr + 1) = ((*(inPixelPtr + 1) ^ *(inPixelPtr + 4)) >> 1) + (*(inPixelPtr + 1) & *(inPixelPtr + 4));
1303 *(outPixelPtr + 2) = ((*(inPixelPtr + 2) ^ *(inPixelPtr + 5)) >> 1) + (*(inPixelPtr + 2) & *(inPixelPtr + 5));
1309 void HalveScanlineInPlaceRGBA8888(unsigned char* const pixels, const unsigned int width)
1311 DebugAssertScanlineParameters(pixels, width);
1312 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1314 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
1316 const unsigned int lastPair = EvenDown(width - 2);
1318 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1320 const uint32_t averaged = AveragePixelRGBA8888(alignedPixels[pixel], alignedPixels[pixel + 1]);
1321 alignedPixels[outPixel] = averaged;
1325 void HalveScanlineInPlaceRGB565(unsigned char* pixels, unsigned int width)
1327 DebugAssertScanlineParameters(pixels, width);
1328 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1330 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
1332 const unsigned int lastPair = EvenDown(width - 2);
1334 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1336 const uint32_t averaged = AveragePixelRGB565(alignedPixels[pixel], alignedPixels[pixel + 1]);
1337 alignedPixels[outPixel] = averaged;
1341 void HalveScanlineInPlace2Bytes(unsigned char* const pixels, const unsigned int width)
1343 DebugAssertScanlineParameters(pixels, width);
1345 const unsigned int lastPair = EvenDown(width - 2);
1347 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1351 * // Load all the byte pixel components we need:
1352 * const unsigned int c11 = pixels[pixel * 2];
1353 * const unsigned int c12 = pixels[pixel * 2 + 1];
1354 * const unsigned int c21 = pixels[pixel * 2 + 2];
1355 * const unsigned int c22 = pixels[pixel * 2 + 3];
1357 * // Save the averaged byte pixel components:
1358 * pixels[outPixel * 2] = static_cast<unsigned char>(AverageComponent(c11, c21));
1359 * pixels[outPixel * 2 + 1] = static_cast<unsigned char>(AverageComponent(c12, c22));
1362 // Note : We can assume that pixel is even number. So we can use | operation instead of + operation.
1363 pixels[(outPixel << 1)] = ((pixels[(pixel << 1)] ^ pixels[(pixel << 1) | 2]) >> 1) + (pixels[(pixel << 1)] & pixels[(pixel << 1) | 2]);
1364 pixels[(outPixel << 1) | 1] = ((pixels[(pixel << 1) | 1] ^ pixels[(pixel << 1) | 3]) >> 1) + (pixels[(pixel << 1) | 1] & pixels[(pixel << 1) | 3]);
1368 void HalveScanlineInPlace1Byte(unsigned char* const pixels, const unsigned int width)
1370 DebugAssertScanlineParameters(pixels, width);
1372 const unsigned int lastPair = EvenDown(width - 2);
1374 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1378 * // Load all the byte pixel components we need:
1379 * const unsigned int c1 = pixels[pixel];
1380 * const unsigned int c2 = pixels[pixel + 1];
1382 * // Save the averaged byte pixel component:
1383 * pixels[outPixel] = static_cast<unsigned char>(AverageComponent(c1, c2));
1386 // Note : We can assume that pixel is even number. So we can use | operation instead of + operation.
1387 pixels[outPixel] = ((pixels[pixel] ^ pixels[pixel | 1]) >> 1) + (pixels[pixel] & pixels[pixel | 1]);
1396 * @copydoc AverageScanlines1
1397 * @note This API average eight components in one operation.
1398 * It will give performance benifit.
1400 inline void AverageScanlinesWithEightComponents(
1401 const unsigned char* const scanline1,
1402 const unsigned char* const __restrict__ scanline2,
1403 unsigned char* const outputScanline,
1404 const unsigned int totalComponentCount)
1406 unsigned int component = 0;
1407 if(DALI_LIKELY(totalComponentCount >= 8))
1409 // Jump 8 components in one step
1410 const std::uint64_t* const scanline18Step = reinterpret_cast<const std::uint64_t* const>(scanline1);
1411 const std::uint64_t* const scanline28Step = reinterpret_cast<const std::uint64_t* const>(scanline2);
1412 std::uint64_t* const output8step = reinterpret_cast<std::uint64_t* const>(outputScanline);
1414 const std::uint32_t totalStepCount = (totalComponentCount) >> 3;
1415 component = totalStepCount << 3;
1417 // and for each step, calculate average of 8 bytes.
1418 for(std::uint32_t i = 0; i < totalStepCount; ++i)
1420 const auto& c1 = *(scanline18Step + i);
1421 const auto& c2 = *(scanline28Step + i);
1422 *(output8step + i) = static_cast<std::uint64_t>((((c1 ^ c2) & 0xfefefefefefefefeull) >> 1) + (c1 & c2));
1425 // remaining components calculate
1426 for(; component < totalComponentCount; ++component)
1428 const auto& c1 = scanline1[component];
1429 const auto& c2 = scanline2[component];
1430 outputScanline[component] = static_cast<std::uint8_t>(((c1 ^ c2) >> 1) + (c1 & c2));
1436 void AverageScanlines1(const unsigned char* const scanline1,
1437 const unsigned char* const __restrict__ scanline2,
1438 unsigned char* const outputScanline,
1439 const unsigned int width)
1441 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width);
1445 * for(unsigned int component = 0; component < width; ++component)
1447 * outputScanline[component] = static_cast<unsigned char>(AverageComponent(scanline1[component], scanline2[component]));
1451 AverageScanlinesWithEightComponents(scanline1, scanline2, outputScanline, width);
1454 void AverageScanlines2(const unsigned char* const scanline1,
1455 const unsigned char* const __restrict__ scanline2,
1456 unsigned char* const outputScanline,
1457 const unsigned int width)
1459 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 2);
1463 * for(unsigned int component = 0; component < width * 2; ++component)
1465 * outputScanline[component] = static_cast<unsigned char>(AverageComponent(scanline1[component], scanline2[component]));
1469 AverageScanlinesWithEightComponents(scanline1, scanline2, outputScanline, width * 2);
1472 void AverageScanlines3(const unsigned char* const scanline1,
1473 const unsigned char* const __restrict__ scanline2,
1474 unsigned char* const outputScanline,
1475 const unsigned int width)
1477 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 3);
1481 * for(unsigned int component = 0; component < width * 3; ++component)
1483 * outputScanline[component] = static_cast<unsigned char>(AverageComponent(scanline1[component], scanline2[component]));
1487 AverageScanlinesWithEightComponents(scanline1, scanline2, outputScanline, width * 3);
1490 void AverageScanlinesRGBA8888(const unsigned char* const scanline1,
1491 const unsigned char* const __restrict__ scanline2,
1492 unsigned char* const outputScanline,
1493 const unsigned int width)
1495 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 4);
1496 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1497 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1498 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1500 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1501 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1502 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1504 for(unsigned int pixel = 0; pixel < width; ++pixel)
1506 alignedOutput[pixel] = AveragePixelRGBA8888(alignedScanline1[pixel], alignedScanline2[pixel]);
1510 void AverageScanlinesRGB565(const unsigned char* const scanline1,
1511 const unsigned char* const __restrict__ scanline2,
1512 unsigned char* const outputScanline,
1513 const unsigned int width)
1515 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 2);
1516 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1517 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1518 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1520 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1521 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1522 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1524 for(unsigned int pixel = 0; pixel < width; ++pixel)
1526 alignedOutput[pixel] = AveragePixelRGB565(alignedScanline1[pixel], alignedScanline2[pixel]);
1530 /// Dispatch to pixel format appropriate box filter downscaling functions.
1531 void DownscaleInPlacePow2(unsigned char* const pixels,
1532 Pixel::Format pixelFormat,
1533 unsigned int inputWidth,
1534 unsigned int inputHeight,
1535 unsigned int inputStride,
1536 unsigned int desiredWidth,
1537 unsigned int desiredHeight,
1538 FittingMode::Type fittingMode,
1539 SamplingMode::Type samplingMode,
1541 unsigned& outHeight,
1542 unsigned& outStride)
1544 outWidth = inputWidth;
1545 outHeight = inputHeight;
1546 outStride = inputStride;
1547 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1548 if(samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR)
1550 // Check the pixel format is one that is supported:
1551 if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1553 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode(fittingMode);
1557 case Pixel::RGBA8888:
1559 Internal::Platform::DownscaleInPlacePow2RGBA8888(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1564 Internal::Platform::DownscaleInPlacePow2RGB888(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1569 Internal::Platform::DownscaleInPlacePow2RGB565(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1574 Internal::Platform::DownscaleInPlacePow2ComponentPair(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1580 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1585 DALI_ASSERT_DEBUG(false && "Inner branch conditions don't match outer branch.");
1592 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat));
1596 void DownscaleInPlacePow2RGB888(unsigned char* pixels,
1597 unsigned int inputWidth,
1598 unsigned int inputHeight,
1599 unsigned int inputStride,
1600 unsigned int desiredWidth,
1601 unsigned int desiredHeight,
1602 BoxDimensionTest dimensionTest,
1604 unsigned& outHeight,
1605 unsigned& outStride)
1607 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1610 void DownscaleInPlacePow2RGBA8888(unsigned char* pixels,
1611 unsigned int inputWidth,
1612 unsigned int inputHeight,
1613 unsigned int inputStride,
1614 unsigned int desiredWidth,
1615 unsigned int desiredHeight,
1616 BoxDimensionTest dimensionTest,
1618 unsigned& outHeight,
1619 unsigned& outStride)
1621 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1622 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1625 void DownscaleInPlacePow2RGB565(unsigned char* pixels,
1626 unsigned int inputWidth,
1627 unsigned int inputHeight,
1628 unsigned int inputStride,
1629 unsigned int desiredWidth,
1630 unsigned int desiredHeight,
1631 BoxDimensionTest dimensionTest,
1632 unsigned int& outWidth,
1633 unsigned int& outHeight,
1634 unsigned int& outStride)
1636 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1640 * @copydoc DownscaleInPlacePow2RGB888
1642 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1644 void DownscaleInPlacePow2ComponentPair(unsigned char* pixels,
1645 unsigned int inputWidth,
1646 unsigned int inputHeight,
1647 unsigned int inputStride,
1648 unsigned int desiredWidth,
1649 unsigned int desiredHeight,
1650 BoxDimensionTest dimensionTest,
1652 unsigned& outHeight,
1653 unsigned& outStride)
1655 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1658 void DownscaleInPlacePow2SingleBytePerPixel(unsigned char* pixels,
1659 unsigned int inputWidth,
1660 unsigned int inputHeight,
1661 unsigned int inputStride,
1662 unsigned int desiredWidth,
1663 unsigned int desiredHeight,
1664 BoxDimensionTest dimensionTest,
1665 unsigned int& outWidth,
1666 unsigned int& outHeight,
1667 unsigned int& outStride)
1669 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1672 // Point sampling group below
1677 * @brief Point sample an image to a new resolution (like GL_NEAREST).
1679 * Template is used purely as a type-safe code generator in this one
1680 * compilation unit. Generated code is inlined into type-specific wrapper
1681 * functions below which are exported to rest of module.
1683 template<typename PIXEL>
1684 inline void PointSampleAddressablePixels(const uint8_t* inPixels,
1685 unsigned int inputWidth,
1686 unsigned int inputHeight,
1687 unsigned int inputStride,
1689 unsigned int desiredWidth,
1690 unsigned int desiredHeight)
1692 DALI_ASSERT_DEBUG(((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1693 outPixels >= inPixels + inputStride * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1694 "The input and output buffers must not overlap for an upscaling.");
1695 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, ...).");
1696 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, ...).");
1698 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1702 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1703 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1704 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1705 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1707 unsigned int inY = 0;
1708 for(unsigned int outY = 0; outY < desiredHeight; ++outY)
1710 // Round fixed point y coordinate to nearest integer:
1711 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1712 const PIXEL* const inScanline = &inAligned[inputStride * integerY];
1713 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1715 DALI_ASSERT_DEBUG(integerY < inputHeight);
1716 DALI_ASSERT_DEBUG(reinterpret_cast<const uint8_t*>(inScanline) < (inPixels + inputStride * inputHeight * sizeof(PIXEL)));
1717 DALI_ASSERT_DEBUG(reinterpret_cast<uint8_t*>(outScanline) < (outPixels + desiredWidth * desiredHeight * sizeof(PIXEL)));
1719 unsigned int inX = 0;
1720 for(unsigned int outX = 0; outX < desiredWidth; ++outX)
1722 // Round the fixed-point x coordinate to an integer:
1723 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1724 const PIXEL* const inPixelAddress = &inScanline[integerX];
1725 const PIXEL pixel = *inPixelAddress;
1726 outScanline[outX] = pixel;
1736 void PointSample4BPP(const unsigned char* inPixels,
1737 unsigned int inputWidth,
1738 unsigned int inputHeight,
1739 unsigned int inputStride,
1740 unsigned char* outPixels,
1741 unsigned int desiredWidth,
1742 unsigned int desiredHeight)
1744 PointSampleAddressablePixels<uint32_t>(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1748 void PointSample2BPP(const unsigned char* inPixels,
1749 unsigned int inputWidth,
1750 unsigned int inputHeight,
1751 unsigned int inputStride,
1752 unsigned char* outPixels,
1753 unsigned int desiredWidth,
1754 unsigned int desiredHeight)
1756 PointSampleAddressablePixels<uint16_t>(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1760 void PointSample1BPP(const unsigned char* inPixels,
1761 unsigned int inputWidth,
1762 unsigned int inputHeight,
1763 unsigned int inputStride,
1764 unsigned char* outPixels,
1765 unsigned int desiredWidth,
1766 unsigned int desiredHeight)
1768 PointSampleAddressablePixels<uint8_t>(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1772 * RGB888 is a special case as its pixels are not aligned addressable units.
1774 void PointSample3BPP(const uint8_t* inPixels,
1775 unsigned int inputWidth,
1776 unsigned int inputHeight,
1777 unsigned int inputStride,
1779 unsigned int desiredWidth,
1780 unsigned int desiredHeight)
1782 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1786 const unsigned int BYTES_PER_PIXEL = 3;
1788 // Generate fixed-point 16.16 deltas in input image coordinates:
1789 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1790 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1792 // Step through output image in whole integer pixel steps while tracking the
1793 // corresponding locations in the input image using 16.16 fixed-point
1795 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1796 for(unsigned int outY = 0; outY < desiredHeight; ++outY)
1798 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1799 const uint8_t* const inScanline = &inPixels[inputStride * integerY * BYTES_PER_PIXEL];
1800 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1801 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1803 for(unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL)
1805 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1806 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1807 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1809 // Issue loads for all pixel color components up-front:
1810 const unsigned int c0 = inPixelAddress[0];
1811 const unsigned int c1 = inPixelAddress[1];
1812 const unsigned int c2 = inPixelAddress[2];
1813 ///@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.
1815 // Output the pixel components:
1816 outScanline[outX] = static_cast<uint8_t>(c0);
1817 outScanline[outX + 1] = static_cast<uint8_t>(c1);
1818 outScanline[outX + 2] = static_cast<uint8_t>(c2);
1820 // Increment the fixed-point input coordinate:
1828 // Dispatch to a format-appropriate point sampling function:
1829 void PointSample(const unsigned char* inPixels,
1830 unsigned int inputWidth,
1831 unsigned int inputHeight,
1832 unsigned int inputStride,
1833 Pixel::Format pixelFormat,
1834 unsigned char* outPixels,
1835 unsigned int desiredWidth,
1836 unsigned int desiredHeight)
1838 // Check the pixel format is one that is supported:
1839 if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1845 PointSample3BPP(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1848 case Pixel::RGBA8888:
1850 PointSample4BPP(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1856 PointSample2BPP(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1862 PointSample1BPP(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1867 DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
1873 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat));
1877 // Linear sampling group below
1881 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1882 inline uint8_t BilinearFilter1BPPByte(uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1884 return static_cast<uint8_t>(BilinearFilter1Component(tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical));
1887 /** @copydoc BilinearFilter1BPPByte */
1888 inline Pixel2Bytes BilinearFilter2Bytes(Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1891 pixel.l = static_cast<uint8_t>(BilinearFilter1Component(tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical));
1892 pixel.a = static_cast<uint8_t>(BilinearFilter1Component(tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical));
1896 /** @copydoc BilinearFilter1BPPByte */
1897 inline Pixel3Bytes BilinearFilterRGB888(Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1900 pixel.r = static_cast<uint8_t>(BilinearFilter1Component(tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical));
1901 pixel.g = static_cast<uint8_t>(BilinearFilter1Component(tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical));
1902 pixel.b = static_cast<uint8_t>(BilinearFilter1Component(tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical));
1906 /** @copydoc BilinearFilter1BPPByte */
1907 inline PixelRGB565 BilinearFilterRGB565(PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1909 const PixelRGB565 pixel = static_cast<PixelRGB565>((BilinearFilter1Component(tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical) << 11u) +
1910 (BilinearFilter1Component((tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical) << 5u) +
1911 BilinearFilter1Component(tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical));
1915 /** @copydoc BilinearFilter1BPPByte */
1916 inline Pixel4Bytes BilinearFilter4Bytes(Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1919 pixel.r = static_cast<uint8_t>(BilinearFilter1Component(tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical));
1920 pixel.g = static_cast<uint8_t>(BilinearFilter1Component(tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical));
1921 pixel.b = static_cast<uint8_t>(BilinearFilter1Component(tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical));
1922 pixel.a = static_cast<uint8_t>(BilinearFilter1Component(tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical));
1927 * @brief Generic version of bilinear sampling image resize function.
1928 * @note Limited to one compilation unit and exposed through type-specific
1929 * wrapper functions below.
1933 PIXEL (*BilinearFilter)(PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical),
1934 bool DEBUG_ASSERT_ALIGNMENT>
1935 inline void LinearSampleGeneric(const unsigned char* __restrict__ inPixels,
1936 ImageDimensions inputDimensions,
1937 unsigned int inputStride,
1938 unsigned char* __restrict__ outPixels,
1939 ImageDimensions desiredDimensions)
1941 const unsigned int inputWidth = inputDimensions.GetWidth();
1942 const unsigned int inputHeight = inputDimensions.GetHeight();
1943 const unsigned int desiredWidth = desiredDimensions.GetWidth();
1944 const unsigned int desiredHeight = desiredDimensions.GetHeight();
1946 DALI_ASSERT_DEBUG(((outPixels >= inPixels + inputStride * inputHeight * sizeof(PIXEL)) ||
1947 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1948 "Input and output buffers cannot overlap.");
1949 if(DEBUG_ASSERT_ALIGNMENT)
1951 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, ...).");
1952 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, ...).");
1955 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1959 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1960 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1961 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1962 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1964 unsigned int inY = 0;
1965 for(unsigned int outY = 0; outY < desiredHeight; ++outY)
1967 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1969 // Find the two scanlines to blend and the weight to blend with:
1970 const unsigned int integerY1 = inY >> 16u;
1971 const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1972 const unsigned int inputYWeight = inY & 65535u;
1974 DALI_ASSERT_DEBUG(integerY1 < inputHeight);
1975 DALI_ASSERT_DEBUG(integerY2 < inputHeight);
1977 const PIXEL* const inScanline1 = &inAligned[inputStride * integerY1];
1978 const PIXEL* const inScanline2 = &inAligned[inputStride * integerY2];
1980 unsigned int inX = 0;
1981 for(unsigned int outX = 0; outX < desiredWidth; ++outX)
1983 // Work out the two pixel scanline offsets for this cluster of four samples:
1984 const unsigned int integerX1 = inX >> 16u;
1985 const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1987 // Execute the loads:
1988 const PIXEL pixel1 = inScanline1[integerX1];
1989 const PIXEL pixel2 = inScanline2[integerX1];
1990 const PIXEL pixel3 = inScanline1[integerX2];
1991 const PIXEL pixel4 = inScanline2[integerX2];
1992 ///@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.
1994 // Weighted bilinear filter:
1995 const unsigned int inputXWeight = inX & 65535u;
1996 outScanline[outX] = BilinearFilter(pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight);
2006 // Format-specific linear scaling instantiations:
2008 void LinearSample1BPP(const unsigned char* __restrict__ inPixels,
2009 ImageDimensions inputDimensions,
2010 unsigned int inputStride,
2011 unsigned char* __restrict__ outPixels,
2012 ImageDimensions desiredDimensions)
2014 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2017 void LinearSample2BPP(const unsigned char* __restrict__ inPixels,
2018 ImageDimensions inputDimensions,
2019 unsigned int inputStride,
2020 unsigned char* __restrict__ outPixels,
2021 ImageDimensions desiredDimensions)
2023 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2026 void LinearSampleRGB565(const unsigned char* __restrict__ inPixels,
2027 ImageDimensions inputDimensions,
2028 unsigned int inputStride,
2029 unsigned char* __restrict__ outPixels,
2030 ImageDimensions desiredDimensions)
2032 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2035 void LinearSample3BPP(const unsigned char* __restrict__ inPixels,
2036 ImageDimensions inputDimensions,
2037 unsigned int inputStride,
2038 unsigned char* __restrict__ outPixels,
2039 ImageDimensions desiredDimensions)
2041 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2044 void LinearSample4BPP(const unsigned char* __restrict__ inPixels,
2045 ImageDimensions inputDimensions,
2046 unsigned int inputStride,
2047 unsigned char* __restrict__ outPixels,
2048 ImageDimensions desiredDimensions)
2050 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2053 void Resample(const unsigned char* __restrict__ inPixels,
2054 ImageDimensions inputDimensions,
2055 unsigned int inputStride,
2056 unsigned char* __restrict__ outPixels,
2057 ImageDimensions desiredDimensions,
2058 Resampler::Filter filterType,
2062 // Got from the test.cpp of the ImageResampler lib.
2063 const float ONE_DIV_255 = 1.0f / 255.0f;
2064 const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
2065 const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
2066 const int ALPHA_CHANNEL = hasAlpha ? (numChannels - 1) : 0;
2068 static bool loadColorSpaces = true;
2069 static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
2070 static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
2072 if(loadColorSpaces) // Only create the color space conversions on the first execution
2074 loadColorSpaces = false;
2076 for(int i = 0; i <= MAX_UNSIGNED_CHAR; ++i)
2078 srgbToLinear[i] = pow(static_cast<float>(i) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA);
2081 const float invLinearToSrgbTableSize = 1.0f / static_cast<float>(LINEAR_TO_SRGB_TABLE_SIZE);
2082 const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
2084 for(int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i)
2086 int k = static_cast<int>(255.0f * pow(static_cast<float>(i) * invLinearToSrgbTableSize, invSourceGamma) + 0.5f);
2091 else if(k > MAX_UNSIGNED_CHAR)
2093 k = MAX_UNSIGNED_CHAR;
2095 linearToSrgb[i] = static_cast<unsigned char>(k);
2099 std::vector<Resampler*> resamplers(numChannels);
2100 std::vector<Vector<float>> samples(numChannels);
2102 const int srcWidth = inputDimensions.GetWidth();
2103 const int srcHeight = inputDimensions.GetHeight();
2104 const int dstWidth = desiredDimensions.GetWidth();
2105 const int dstHeight = desiredDimensions.GetHeight();
2107 // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
2108 // used for the other components (a memory and slight cache efficiency optimization).
2109 resamplers[0] = new Resampler(srcWidth,
2113 Resampler::BOUNDARY_CLAMP,
2114 0.0f, // sample_low,
2115 1.0f, // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
2116 filterType, // The type of filter.
2118 NULL, // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
2119 FILTER_SCALE, // src_x_ofs,
2120 FILTER_SCALE); // src_y_ofs. Offset input image by specified amount (fractional values okay).
2121 samples[0].ResizeUninitialized(srcWidth);
2122 for(int i = 1; i < numChannels; ++i)
2124 resamplers[i] = new Resampler(srcWidth,
2128 Resampler::BOUNDARY_CLAMP,
2132 resamplers[0]->get_clist_x(),
2133 resamplers[0]->get_clist_y(),
2136 samples[i].ResizeUninitialized(srcWidth);
2139 const int srcPitch = inputStride * numChannels;
2140 const int dstPitch = dstWidth * numChannels;
2143 for(int srcY = 0; srcY < srcHeight; ++srcY)
2145 const unsigned char* pSrc = &inPixels[srcY * srcPitch];
2147 for(int x = 0; x < srcWidth; ++x)
2149 for(int c = 0; c < numChannels; ++c)
2151 if(c == ALPHA_CHANNEL && hasAlpha)
2153 samples[c][x] = *pSrc++ * ONE_DIV_255;
2157 samples[c][x] = srgbToLinear[*pSrc++];
2162 for(int c = 0; c < numChannels; ++c)
2164 if(!resamplers[c]->put_line(&samples[c][0]))
2166 DALI_ASSERT_DEBUG(!"Out of memory");
2173 for(compIndex = 0; compIndex < numChannels; ++compIndex)
2175 const float* pOutputSamples = resamplers[compIndex]->get_line();
2181 const bool isAlphaChannel = (compIndex == ALPHA_CHANNEL && hasAlpha);
2182 DALI_ASSERT_DEBUG(dstY < dstHeight);
2183 unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
2185 for(int x = 0; x < dstWidth; ++x)
2189 int c = static_cast<int>(255.0f * pOutputSamples[x] + 0.5f);
2194 else if(c > MAX_UNSIGNED_CHAR)
2196 c = MAX_UNSIGNED_CHAR;
2198 *pDst = static_cast<unsigned char>(c);
2202 int j = static_cast<int>(LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f);
2207 else if(j >= LINEAR_TO_SRGB_TABLE_SIZE)
2209 j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
2211 *pDst = linearToSrgb[j];
2214 pDst += numChannels;
2217 if(compIndex < numChannels)
2226 // Delete the resamplers.
2227 for(int i = 0; i < numChannels; ++i)
2229 delete resamplers[i];
2233 void LanczosSample4BPP(const unsigned char* __restrict__ inPixels,
2234 ImageDimensions inputDimensions,
2235 unsigned int inputStride,
2236 unsigned char* __restrict__ outPixels,
2237 ImageDimensions desiredDimensions)
2239 Resample(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true);
2242 void LanczosSample1BPP(const unsigned char* __restrict__ inPixels,
2243 ImageDimensions inputDimensions,
2244 unsigned int inputStride,
2245 unsigned char* __restrict__ outPixels,
2246 ImageDimensions desiredDimensions)
2249 Resample(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false);
2252 // Dispatch to a format-appropriate linear sampling function:
2253 void LinearSample(const unsigned char* __restrict__ inPixels,
2254 ImageDimensions inDimensions,
2255 unsigned int inStride,
2256 Pixel::Format pixelFormat,
2257 unsigned char* __restrict__ outPixels,
2258 ImageDimensions outDimensions)
2260 // Check the pixel format is one that is supported:
2261 if(pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565)
2267 LinearSample3BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2270 case Pixel::RGBA8888:
2272 LinearSample4BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2278 LinearSample1BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2283 LinearSample2BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2288 LinearSampleRGB565(inPixels, inDimensions, inStride, outPixels, outDimensions);
2293 DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
2299 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat));
2303 void RotateByShear(const uint8_t* const pixelsIn,
2304 unsigned int widthIn,
2305 unsigned int heightIn,
2306 unsigned int strideIn,
2307 unsigned int pixelSize,
2309 uint8_t*& pixelsOut,
2310 unsigned int& widthOut,
2311 unsigned int& heightOut)
2313 // @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
2315 // Do first the fast rotations to transform the angle into a (-45..45] range.
2317 float fastRotationPerformed = false;
2318 if((radians > Math::PI_4) && (radians <= RAD_135))
2320 // Angle in (45.0 .. 135.0]
2321 // Rotate image by 90 degrees into temporary image,
2322 // so it requires only an extra rotation angle
2323 // of -45.0 .. +45.0 to complete rotation.
2324 fastRotationPerformed = Rotate90(pixelsIn,
2333 if(!fastRotationPerformed)
2335 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2336 // The fast rotation failed.
2340 radians -= Math::PI_2;
2342 else if((radians > RAD_135) && (radians <= RAD_225))
2344 // Angle in (135.0 .. 225.0]
2345 // Rotate image by 180 degrees into temporary image,
2346 // so it requires only an extra rotation angle
2347 // of -45.0 .. +45.0 to complete rotation.
2349 fastRotationPerformed = Rotate180(pixelsIn,
2356 if(!fastRotationPerformed)
2358 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2359 // The fast rotation failed.
2363 radians -= Math::PI;
2365 heightOut = heightIn;
2367 else if((radians > RAD_225) && (radians <= RAD_315))
2369 // Angle in (225.0 .. 315.0]
2370 // Rotate image by 270 degrees into temporary image,
2371 // so it requires only an extra rotation angle
2372 // of -45.0 .. +45.0 to complete rotation.
2374 fastRotationPerformed = Rotate270(pixelsIn,
2383 if(!fastRotationPerformed)
2385 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2386 // The fast rotation failed.
2393 if(fabs(radians) < Dali::Math::MACHINE_EPSILON_10)
2395 // Nothing else to do if the angle is zero.
2396 // The rotation angle was 90, 180 or 270.
2398 // @note Allocated memory by 'Fast Rotations', if any, has to be freed by the called to this function.
2402 const uint8_t* const firstHorizontalSkewPixelsIn = fastRotationPerformed ? pixelsOut : pixelsIn;
2403 std::unique_ptr<uint8_t, void (*)(void*)> tmpPixelsInPtr((fastRotationPerformed ? pixelsOut : nullptr), free);
2405 unsigned int stride = fastRotationPerformed ? widthOut : strideIn;
2407 // Reset the input/output
2409 heightIn = heightOut;
2410 pixelsOut = nullptr;
2412 const float angleSinus = sin(radians);
2413 const float angleCosinus = cos(radians);
2414 const float angleTangent = tan(0.5f * radians);
2416 ///////////////////////////////////////
2417 // Perform 1st shear (horizontal)
2418 ///////////////////////////////////////
2420 // Calculate first shear (horizontal) destination image dimensions
2422 widthOut = widthIn + static_cast<unsigned int>(fabs(angleTangent) * static_cast<float>(heightIn));
2423 heightOut = heightIn;
2425 // Allocate the buffer for the 1st shear
2426 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2428 if(nullptr == pixelsOut)
2433 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2435 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Fast rotations'.
2436 // Nothing else to do if the memory allocation fails.
2440 for(unsigned int y = 0u; y < heightOut; ++y)
2442 const float shear = angleTangent * ((angleTangent >= 0.f) ? (0.5f + static_cast<float>(y)) : (0.5f + static_cast<float>(y) - static_cast<float>(heightOut)));
2444 const int intShear = static_cast<int>(floor(shear));
2445 HorizontalSkew(firstHorizontalSkewPixelsIn, widthIn, stride, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>(intShear));
2448 // Reset the 'pixel in' pointer with the output of the 'First Horizontal Skew' and free the memory allocated by the 'Fast Rotations'.
2449 tmpPixelsInPtr.reset(pixelsOut);
2450 unsigned int tmpWidthIn = widthOut;
2451 unsigned int tmpHeightIn = heightOut;
2453 // Reset the input/output
2454 pixelsOut = nullptr;
2456 ///////////////////////////////////////
2457 // Perform 2nd shear (vertical)
2458 ///////////////////////////////////////
2460 // Calc 2nd shear (vertical) destination image dimensions
2461 heightOut = static_cast<unsigned int>(static_cast<float>(widthIn) * fabs(angleSinus) + static_cast<float>(heightIn) * angleCosinus);
2463 // Allocate the buffer for the 2nd shear
2464 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2466 if(nullptr == pixelsOut)
2471 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2472 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'First Horizontal Skew'.
2473 // Nothing else to do if the memory allocation fails.
2477 // Variable skew offset
2478 float offset = angleSinus * ((angleSinus > 0.f) ? static_cast<float>(widthIn - 1u) : -(static_cast<float>(widthIn) - static_cast<float>(widthOut)));
2480 unsigned int column = 0u;
2481 for(column = 0u; column < widthOut; ++column, offset -= angleSinus)
2483 const int shear = static_cast<int>(floor(offset));
2484 VerticalSkew(tmpPixelsInPtr.get(), tmpWidthIn, tmpHeightIn, tmpWidthIn, pixelSize, pixelsOut, widthOut, heightOut, column, shear, offset - static_cast<float>(shear));
2486 // Reset the 'pixel in' pointer with the output of the 'Vertical Skew' and free the memory allocated by the 'First Horizontal Skew'.
2487 // Reset the input/output
2488 tmpPixelsInPtr.reset(pixelsOut);
2489 tmpWidthIn = widthOut;
2490 tmpHeightIn = heightOut;
2491 pixelsOut = nullptr;
2493 ///////////////////////////////////////
2494 // Perform 3rd shear (horizontal)
2495 ///////////////////////////////////////
2497 // Calc 3rd shear (horizontal) destination image dimensions
2498 widthOut = static_cast<unsigned int>(static_cast<float>(heightIn) * fabs(angleSinus) + static_cast<float>(widthIn) * angleCosinus) + 1u;
2500 // Allocate the buffer for the 3rd shear
2501 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2503 if(nullptr == pixelsOut)
2508 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2509 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2510 // Nothing else to do if the memory allocation fails.
2514 offset = (angleSinus >= 0.f) ? -angleSinus * angleTangent * static_cast<float>(widthIn - 1u) : angleTangent * (static_cast<float>(widthIn - 1u) * -angleSinus + (1.f - static_cast<float>(heightOut)));
2516 for(unsigned int y = 0u; y < heightOut; ++y, offset += angleTangent)
2518 const int shear = static_cast<int>(floor(offset));
2519 HorizontalSkew(tmpPixelsInPtr.get(), tmpWidthIn, tmpWidthIn, pixelSize, pixelsOut, widthOut, y, shear, offset - static_cast<float>(shear));
2522 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2523 // @note Allocated memory by the last 'Horizontal Skew' has to be freed by the caller to this function.
2526 void HorizontalShear(const uint8_t* const pixelsIn,
2527 unsigned int widthIn,
2528 unsigned int heightIn,
2529 unsigned int strideIn,
2530 unsigned int pixelSize,
2532 uint8_t*& pixelsOut,
2533 unsigned int& widthOut,
2534 unsigned int& heightOut)
2536 // Calculate the destination image dimensions.
2538 const float absRadians = fabs(radians);
2540 if(absRadians > Math::PI_4)
2542 // Can't shear more than 45 degrees.
2546 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Can't shear more than 45 degrees (PI/4 radians). radians : %f\n", radians);
2550 widthOut = widthIn + static_cast<unsigned int>(ceil(absRadians * static_cast<float>(heightIn)));
2551 heightOut = heightIn;
2553 // Allocate the buffer for the shear.
2554 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2556 if(nullptr == pixelsOut)
2561 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2565 for(unsigned int y = 0u; y < heightOut; ++y)
2567 const float shear = radians * ((radians >= 0.f) ? (0.5f + static_cast<float>(y)) : (0.5f + static_cast<float>(y) - static_cast<float>(heightOut)));
2569 const int intShear = static_cast<int>(floor(shear));
2570 HorizontalSkew(pixelsIn, widthIn, strideIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>(intShear));
2574 } /* namespace Platform */
2575 } /* namespace Internal */
2576 } /* namespace Dali */