2 * Copyright (c) 2024 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/integration-api/trace.h>
24 #include <dali/public-api/common/dali-vector.h>
25 #include <dali/public-api/math/vector2.h>
27 #include <third-party/resampler/resampler.h>
43 // The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
44 // A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
45 // We can optionally use a Vector4 color here, but at reduced fill speed.
46 const uint8_t BORDER_FILL_VALUE(0x00);
47 // A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
48 const uint32_t MAXIMUM_TARGET_BITMAP_SIZE((1u << 16) - 1);
50 // Constants used by the ImageResampler.
51 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.
52 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.
54 const float RAD_135 = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians;
55 const float RAD_225 = RAD_135 + Math::PI_2; ///< 225 degrees in radians;
56 const float RAD_270 = 3.f * Math::PI_2; ///< 270 degrees in radians;
57 const float RAD_315 = RAD_225 + Math::PI_2; ///< 315 degrees in radians;
59 using Integration::Bitmap;
60 using Integration::BitmapPtr;
61 typedef uint8_t PixelBuffer;
63 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_IMAGE_PERFORMANCE_MARKER, false);
66 * @brief 4 byte pixel structure.
74 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
77 * @brief RGB888 pixel structure.
84 } __attribute__((packed, aligned(1)));
87 * @brief RGB565 pixel typedefed from a short.
89 * Access fields by manual shifting and masking.
91 typedef uint16_t PixelRGB565;
94 * @brief a Pixel composed of two independent byte components.
100 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
102 #if defined(DEBUG_ENABLED)
104 * Disable logging of image operations or make it verbose from the commandline
105 * as follows (e.g., for dali demo app):
107 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
108 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
111 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS");
114 /** @return The greatest even number less than or equal to the argument. */
115 inline uint32_t EvenDown(const uint32_t a)
117 const uint32_t evened = a & ~1u;
122 * @brief Log bad parameters.
124 void ValidateScalingParameters(const uint32_t inputWidth,
125 const uint32_t inputHeight,
126 const uint32_t desiredWidth,
127 const uint32_t desiredHeight)
129 if(desiredWidth > inputWidth || desiredHeight > inputHeight)
131 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight);
134 if(desiredWidth == 0u || desiredHeight == 0u)
136 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n");
139 if(inputWidth == 0u || inputHeight == 0u)
141 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n");
146 * @brief Do debug assertions common to all scanline halving functions.
147 * @note Inline and in anon namespace so should boil away in release builds.
149 inline void DebugAssertScanlineParameters(const uint8_t* const pixels, const uint32_t width)
151 DALI_ASSERT_DEBUG(pixels && "Null pointer.");
152 DALI_ASSERT_DEBUG(width > 1u && "Can't average fewer than two pixels.");
153 DALI_ASSERT_DEBUG(width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?");
157 * @brief Assertions on params to functions averaging pairs of scanlines.
158 * @note Inline as intended to boil away in release.
160 inline void DebugAssertDualScanlineParameters(const uint8_t* const scanline1,
161 const uint8_t* const scanline2,
162 uint8_t* const outputScanline,
163 const size_t widthInComponents)
165 DALI_ASSERT_DEBUG(scanline1 && "Null pointer.");
166 DALI_ASSERT_DEBUG(scanline2 && "Null pointer.");
167 DALI_ASSERT_DEBUG(outputScanline && "Null pointer.");
168 DALI_ASSERT_DEBUG(((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents)) && "Scanlines alias.");
169 DALI_ASSERT_DEBUG(((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output.");
173 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
175 BoxDimensionTest DimensionTestForScalingMode(FittingMode::Type fittingMode)
177 BoxDimensionTest dimensionTest;
178 dimensionTest = BoxDimensionTestEither;
182 // Shrink to fit attempts to make one or zero dimensions smaller than the
183 // desired dimensions and one or two dimensions exactly the same as the desired
184 // ones, so as long as one dimension is larger than the desired size, box
185 // filtering can continue even if the second dimension is smaller than the
186 // desired dimensions:
187 case FittingMode::SHRINK_TO_FIT:
189 dimensionTest = BoxDimensionTestEither;
192 // Scale to fill mode keeps both dimensions at least as large as desired:
193 case FittingMode::SCALE_TO_FILL:
194 case FittingMode::VISUAL_FITTING:
196 dimensionTest = BoxDimensionTestBoth;
199 // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
200 case FittingMode::FIT_WIDTH:
202 dimensionTest = BoxDimensionTestX;
205 // X Dimension is ignored by definition in FIT_HEIGHT mode:
206 case FittingMode::FIT_HEIGHT:
208 dimensionTest = BoxDimensionTestY;
213 return dimensionTest;
217 * @brief Work out the dimensions for a uniform scaling of the input to map it
218 * into the target while effecting ShinkToFit scaling mode.
220 ImageDimensions FitForShrinkToFit(ImageDimensions target, ImageDimensions source)
222 // Scale the input by the least extreme of the two dimensions:
223 const float widthScale = target.GetX() / float(source.GetX());
224 const float heightScale = target.GetY() / float(source.GetY());
225 const float scale = widthScale < heightScale ? widthScale : heightScale;
227 // Do no scaling at all if the result would increase area:
233 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
237 * @brief Work out the dimensions for a uniform scaling of the input to map it
238 * into the target while effecting SCALE_TO_FILL scaling mode.
239 * @note An image scaled into the output dimensions will need either top and
240 * bottom or left and right to be cropped away unless the source was pre-cropped
241 * to match the destination aspect ratio.
243 ImageDimensions FitForScaleToFill(ImageDimensions target, ImageDimensions source)
245 DALI_ASSERT_DEBUG(source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in");
246 // Scale the input by the least extreme of the two dimensions:
247 const float widthScale = target.GetX() / float(source.GetX());
248 const float heightScale = target.GetY() / float(source.GetY());
249 const float scale = widthScale > heightScale ? widthScale : heightScale;
251 // Do no scaling at all if the result would increase area:
257 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
261 * @brief Work out the dimensions for a uniform scaling of the input to map it
262 * into the target while effecting FIT_WIDTH scaling mode.
264 ImageDimensions FitForFitWidth(ImageDimensions target, ImageDimensions source)
266 DALI_ASSERT_DEBUG(source.GetX() > 0 && "Cant fit a zero-dimension rectangle.");
267 const float scale = target.GetX() / float(source.GetX());
269 // Do no scaling at all if the result would increase area:
274 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
278 * @brief Work out the dimensions for a uniform scaling of the input to map it
279 * into the target while effecting FIT_HEIGHT scaling mode.
281 ImageDimensions FitForFitHeight(ImageDimensions target, ImageDimensions source)
283 DALI_ASSERT_DEBUG(source.GetY() > 0 && "Cant fit a zero-dimension rectangle.");
284 const float scale = target.GetY() / float(source.GetY());
286 // Do no scaling at all if the result would increase area:
292 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
296 * @brief Generate the rectangle to use as the target of a pixel sampling pass
297 * (e.g., nearest or linear).
299 ImageDimensions FitToScalingMode(ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode)
301 ImageDimensions fitDimensions;
304 case FittingMode::SHRINK_TO_FIT:
306 fitDimensions = FitForShrinkToFit(requestedSize, sourceSize);
309 case FittingMode::SCALE_TO_FILL:
310 case FittingMode::VISUAL_FITTING:
312 fitDimensions = FitForScaleToFill(requestedSize, sourceSize);
315 case FittingMode::FIT_WIDTH:
317 fitDimensions = FitForFitWidth(requestedSize, sourceSize);
320 case FittingMode::FIT_HEIGHT:
322 fitDimensions = FitForFitHeight(requestedSize, sourceSize);
327 return fitDimensions;
331 * @brief Calculate the number of lines on the X and Y axis that need to be
332 * either added or removed with repect to the specified fitting mode.
333 * (e.g., nearest or linear).
334 * @param[in] sourceSize The size of the source image
335 * @param[in] fittingMode The fitting mode to use
336 * @param[in/out] requestedSize The target size that the image will be fitted to.
337 * If the source image is smaller than the requested size, the source is not scaled up.
338 * So we reduce the target size while keeping aspect by lowering resolution.
339 * @param[out] scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
340 * @param[out] columnsToCrop The number of columns to remove from the image (can be negative to represent X borders required)
342 void CalculateBordersFromFittingMode(ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop)
344 const int sourceWidth(static_cast<int>(sourceSize.GetWidth()));
345 const int sourceHeight(static_cast<int>(sourceSize.GetHeight()));
346 const float targetAspect(static_cast<float>(requestedSize.GetWidth()) / static_cast<float>(requestedSize.GetHeight()));
352 case FittingMode::FIT_WIDTH:
354 finalWidth = sourceWidth;
355 finalHeight = static_cast<int>(static_cast<float>(sourceWidth) / targetAspect);
359 case FittingMode::FIT_HEIGHT:
361 finalWidth = static_cast<int>(static_cast<float>(sourceHeight) * targetAspect);
362 finalHeight = sourceHeight;
366 case FittingMode::SHRINK_TO_FIT:
368 const float sourceAspect(static_cast<float>(sourceWidth) / static_cast<float>(sourceHeight));
369 if(sourceAspect > targetAspect)
371 finalWidth = sourceWidth;
372 finalHeight = static_cast<int>(static_cast<float>(sourceWidth) / targetAspect);
376 finalWidth = static_cast<int>(static_cast<float>(sourceHeight) * targetAspect);
377 finalHeight = sourceHeight;
382 case FittingMode::SCALE_TO_FILL:
383 case FittingMode::VISUAL_FITTING:
385 const float sourceAspect(static_cast<float>(sourceWidth) / static_cast<float>(sourceHeight));
386 if(sourceAspect > targetAspect)
388 finalWidth = static_cast<int>(static_cast<float>(sourceHeight) * targetAspect);
389 finalHeight = sourceHeight;
393 finalWidth = sourceWidth;
394 finalHeight = static_cast<int>(static_cast<float>(sourceWidth) / targetAspect);
400 // Clamp if overflowed
401 if(DALI_UNLIKELY(finalWidth > std::numeric_limits<uint16_t>::max()))
403 finalWidth = std::numeric_limits<uint16_t>::max();
405 if(DALI_UNLIKELY(finalHeight > std::numeric_limits<uint16_t>::max()))
407 finalHeight = std::numeric_limits<uint16_t>::max();
410 columnsToCrop = -(finalWidth - sourceWidth);
411 scanlinesToCrop = -(finalHeight - sourceHeight);
413 requestedSize.SetWidth(static_cast<uint16_t>(finalWidth));
414 requestedSize.SetHeight(static_cast<uint16_t>(finalHeight));
418 * @brief Construct a pixel buffer object from a copy of the pixel array passed in.
420 Dali::Devel::PixelBuffer MakePixelBuffer(const uint8_t* const pixels, Pixel::Format pixelFormat, uint32_t width, uint32_t height)
422 DALI_ASSERT_DEBUG(pixels && "Null bitmap buffer to copy.");
424 // Allocate a pixel buffer to hold the image passed in:
425 auto newBitmap = Dali::Devel::PixelBuffer::New(width, height, pixelFormat);
427 // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
428 memcpy(newBitmap.GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel(pixelFormat));
433 * @brief Work out the desired width and height, accounting for zeros.
435 * @param[in] bitmapWidth Width of image before processing.
436 * @param[in] bitmapHeight Height of image before processing.
437 * @param[in] requestedWidth Width of area to scale image into. Can be zero.
438 * @param[in] requestedHeight Height of area to scale image into. Can be zero.
439 * @return Dimensions of area to scale image into after special rules are applied.
441 ImageDimensions CalculateDesiredDimensions(uint32_t bitmapWidth, uint32_t bitmapHeight, uint32_t requestedWidth, uint32_t requestedHeight, FittingMode::Type fittingMode)
443 uint32_t maxSize = Dali::GetMaxTextureSize();
445 // If no dimensions have been requested, default to the source ones:
446 if(requestedWidth == 0 && requestedHeight == 0)
448 if(bitmapWidth <= maxSize && bitmapHeight <= maxSize)
450 return ImageDimensions(bitmapWidth, bitmapHeight);
454 // Calculate the size from the max texture size and the source image aspect ratio
455 if(bitmapWidth > bitmapHeight)
457 return ImageDimensions(maxSize, bitmapHeight * maxSize / static_cast<float>(bitmapWidth) + 0.5f);
461 return ImageDimensions(bitmapWidth * maxSize / static_cast<float>(bitmapHeight) + 0.5f, maxSize);
466 // If both dimensions have values requested, use them both:
467 if(requestedWidth != 0 && requestedHeight != 0)
469 DALI_ASSERT_DEBUG( (bitmapWidth > 0 && bitmapHeight > 0) && "Bitmap dimensions are zero");
471 if(fittingMode == FittingMode::VISUAL_FITTING)
473 uint32_t adjustedDesiredWidth, adjustedDesiredHeight;
474 float aspectOfDesiredSize = (float)requestedHeight / (float)requestedWidth;
475 float aspectOfImageSize = (float)bitmapHeight / (float)bitmapWidth;
476 if (aspectOfImageSize > aspectOfDesiredSize)
478 adjustedDesiredWidth = requestedWidth;
479 adjustedDesiredHeight = static_cast<uint64_t>(bitmapHeight) * requestedWidth / bitmapWidth;
483 adjustedDesiredWidth = static_cast<uint64_t>(bitmapWidth) * requestedHeight / bitmapHeight;
484 adjustedDesiredHeight = requestedHeight;
487 requestedWidth = adjustedDesiredWidth;
488 requestedHeight = adjustedDesiredHeight;
491 if(requestedWidth <= maxSize && requestedHeight <= maxSize)
493 return ImageDimensions(requestedWidth, requestedHeight);
497 // Calculate the size from the max texture size and the source image aspect ratio
498 if(requestedWidth > requestedHeight)
500 return ImageDimensions(maxSize, requestedHeight * maxSize / static_cast<float>(requestedWidth) + 0.5f);
504 return ImageDimensions(requestedWidth * maxSize / static_cast<float>(requestedHeight) + 0.5f, maxSize);
509 // Only one of the dimensions has been requested. Calculate the other from
510 // the requested one and the source image aspect ratio:
511 if(requestedWidth != 0)
513 requestedWidth = std::min(requestedWidth, maxSize);
514 return ImageDimensions(requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f);
517 requestedHeight = std::min(requestedHeight, maxSize);
518 return ImageDimensions(bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight);
522 * @brief Rotates the given buffer @p pixelsIn 90 degrees counter clockwise.
524 * @note It allocates memory for the returned @p pixelsOut buffer.
525 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
526 * @note It may fail if malloc() fails to allocate memory.
528 * @param[in] pixelsIn The input buffer.
529 * @param[in] widthIn The width of the input buffer.
530 * @param[in] heightIn The height of the input buffer.
531 * @param[in] strideIn The stride of the input buffer.
532 * @param[in] pixelSize The size of the pixel.
533 * @param[out] pixelsOut The rotated output buffer.
534 * @param[out] widthOut The width of the output buffer.
535 * @param[out] heightOut The height of the output buffer.
537 * @return Whether the rotation succeeded.
539 bool Rotate90(const uint8_t* const pixelsIn,
548 // The new size of the image.
552 // Allocate memory for the rotated buffer.
553 // Output buffer is tightly packed
554 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
555 if(nullptr == pixelsOut)
557 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x %u\n", widthOut, heightOut, pixelSize);
561 // Return if the memory allocations fails.
565 DALI_TRACE_SCOPE(gTraceFilter, "DALI_BITMAP_ROTATE_90");
567 // Rotate the buffer.
568 for(uint32_t y = 0u; y < heightIn; ++y)
570 const uint32_t srcLineIndex = y * strideIn;
571 const uint32_t dstX = y;
572 for(uint32_t x = 0u; x < widthIn; ++x)
574 const uint32_t dstY = heightOut - x - 1u;
575 const uint32_t dstIndex = pixelSize * (dstY * widthOut + dstX);
576 const uint32_t srcIndex = pixelSize * (srcLineIndex + x);
578 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
580 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
589 * @brief Rotates the given buffer @p pixelsIn 180 degrees counter clockwise.
591 * @note It allocates memory for the returned @p pixelsOut buffer.
592 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
593 * @note It may fail if malloc() fails to allocate memory.
595 * @param[in] pixelsIn The input buffer.
596 * @param[in] widthIn The width of the input buffer.
597 * @param[in] heightIn The height of the input buffer.
598 * @param[in] strideIn The stride of the input buffer.
599 * @param[in] pixelSize The size of the pixel.
600 * @param[out] pixelsOut The rotated output buffer.
602 * @return Whether the rotation succeeded.
604 bool Rotate180(const uint8_t* const pixelsIn,
611 // Allocate memory for the rotated buffer.
612 // Output buffer is tightly packed
613 pixelsOut = static_cast<uint8_t*>(malloc(widthIn * heightIn * pixelSize));
614 if(nullptr == pixelsOut)
616 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x %u\n", widthIn, heightIn, pixelSize);
617 // Return if the memory allocations fails.
621 DALI_TRACE_SCOPE(gTraceFilter, "DALI_BITMAP_ROTATE_180");
623 // Rotate the buffer.
624 for(uint32_t y = 0u; y < heightIn; ++y)
626 const uint32_t srcLineIndex = y * strideIn;
627 const uint32_t dstY = heightIn - y - 1u;
628 for(uint32_t x = 0u; x < widthIn; ++x)
630 const uint32_t dstX = widthIn - x - 1u;
631 const uint32_t dstIndex = pixelSize * (dstY * widthIn + dstX);
632 const uint32_t srcIndex = pixelSize * (srcLineIndex + x);
634 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
636 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
645 * @brief Rotates the given buffer @p pixelsIn 270 degrees counter clockwise.
647 * @note It allocates memory for the returned @p pixelsOut buffer.
648 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
649 * @note It may fail if malloc() fails to allocate memory.
651 * @param[in] pixelsIn The input buffer.
652 * @param[in] widthIn The width of the input buffer.
653 * @param[in] heightIn The height of the input buffer.
654 * @param[in] strideIn The stride of the input buffer.
655 * @param[in] pixelSize The size of the pixel.
656 * @param[out] pixelsOut The rotated output buffer.
657 * @param[out] widthOut The width of the output buffer.
658 * @param[out] heightOut The height of the output buffer.
660 * @return Whether the rotation succeeded.
662 bool Rotate270(const uint8_t* const pixelsIn,
671 // The new size of the image.
675 // Allocate memory for the rotated buffer.
676 // Output buffer is tightly packed
677 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
678 if(nullptr == pixelsOut)
680 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x %u\n", widthOut, heightOut, pixelSize);
684 // Return if the memory allocations fails.
688 DALI_TRACE_SCOPE(gTraceFilter, "DALI_BITMAP_ROTATE_270");
690 // Rotate the buffer.
691 for(uint32_t y = 0u; y < heightIn; ++y)
693 const uint32_t srcLineIndex = y * strideIn;
694 const uint32_t dstX = widthOut - y - 1u;
695 for(uint32_t x = 0u; x < widthIn; ++x)
697 const uint32_t dstY = x;
698 const uint32_t dstIndex = pixelSize * (dstY * widthOut + dstX);
699 const uint32_t srcIndex = pixelSize * (srcLineIndex + x);
701 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
703 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
712 * @brief Skews a row horizontally (with filtered weights)
714 * @note Limited to 45 degree skewing only.
715 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
717 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
718 * @param[in] srcWidth The width of the input pixel buffer.
719 * @param[in] srcStride The stride of the input pixel buffer.
720 * @param[in] pixelSize The size of the pixel.
721 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
722 * @param[in] dstWidth The width of the output pixel buffer.
723 * @param[in] row The row index.
724 * @param[in] offset The skew offset.
725 * @param[in] weight The relative weight of right pixel.
727 void HorizontalSkew(const uint8_t* const srcBufferPtr,
731 uint8_t*& dstBufferPtr,
737 DALI_TRACE_SCOPE(gTraceFilter, "DALI_BITMAP_HORIZONTAL_SKEW");
740 // Fill gap left of skew with background.
741 memset(dstBufferPtr + row * pixelSize * dstWidth, 0u, pixelSize * offset);
744 uint8_t oldLeft[4u] = {0u, 0u, 0u, 0u};
746 for(uint32_t i = 0u; i < srcWidth; ++i)
748 // Loop through row pixels
749 const uint32_t srcIndex = pixelSize * (row * srcStride + i);
751 uint8_t src[4u] = {0u, 0u, 0u, 0u};
752 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
754 src[channel] = *(srcBufferPtr + srcIndex + channel);
758 uint8_t left[4u] = {0u, 0u, 0u, 0u};
759 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
761 left[channel] = static_cast<uint8_t>(static_cast<float>(src[channel]) * weight);
763 // Update left over on source
764 src[channel] -= (left[channel] - oldLeft[channel]);
768 if((static_cast<int32_t>(i) + offset >= 0) && (i + offset < dstWidth))
770 const uint32_t dstIndex = pixelSize * (row * dstWidth + i + offset);
772 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
774 *(dstBufferPtr + dstIndex + channel) = src[channel];
778 // Save leftover for next pixel in scan
779 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
781 oldLeft[channel] = left[channel];
785 // Go to rightmost point of skew
786 int32_t i = std::max(static_cast<int32_t>(srcWidth) + offset, -static_cast<int32_t>(dstWidth * row));
787 if(i < static_cast<int32_t>(dstWidth))
789 // If still in image bounds, put leftovers there
790 const uint32_t dstIndex = pixelSize * (row * dstWidth + i);
792 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
794 *(dstBufferPtr + dstIndex + channel) = oldLeft[channel];
797 // Clear to the right of the skewed line with background
799 memset(dstBufferPtr + pixelSize * (row * dstWidth + i), 0u, pixelSize * (dstWidth - i));
804 * @brief Skews a column vertically (with filtered weights)
806 * @note Limited to 45 degree skewing only.
807 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
809 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
810 * @param[in] srcWidth The width of the input pixel buffer.
811 * @param[in] srcHeight The height of the input pixel buffer.
812 * @param[in] srcStride The stride of the input pixel buffer.
813 * @param[in] pixelSize The size of the pixel.
814 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
815 * @param[in] dstWidth The width of the output pixel buffer.
816 * @param[in] dstHeight The height of the output pixel buffer.
817 * @param[in] column The column index.
818 * @param[in] offset The skew offset.
819 * @param[in] weight The relative weight of uppeer pixel.
821 void VerticalSkew(const uint8_t* const srcBufferPtr,
826 uint8_t*& dstBufferPtr,
833 DALI_TRACE_SCOPE(gTraceFilter, "DALI_BITMAP_VERTICAL_SKEW");
834 for(int32_t i = 0; i < offset; ++i)
836 // Fill gap above skew with background
837 const uint32_t dstIndex = pixelSize * (i * dstWidth + column);
839 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
841 *(dstBufferPtr + dstIndex + channel) = 0u;
845 uint8_t oldLeft[4u] = {0u, 0u, 0u, 0u};
849 for(uint32_t i = 0u; i < srcHeight; ++i)
851 // Loop through column pixels
852 const uint32_t srcIndex = pixelSize * (i * srcStride + column);
854 uint8_t src[4u] = {0u, 0u, 0u, 0u};
855 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
857 src[channel] = *(srcBufferPtr + srcIndex + channel);
860 yPos = static_cast<int32_t>(i) + offset;
863 uint8_t left[4u] = {0u, 0u, 0u, 0u};
864 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
866 left[channel] = static_cast<uint8_t>(static_cast<float>(src[channel]) * weight);
867 // Update left over on source
868 src[channel] -= (left[channel] - oldLeft[channel]);
872 if((yPos >= 0) && (yPos < static_cast<int32_t>(dstHeight)))
874 const uint32_t dstIndex = pixelSize * (yPos * dstWidth + column);
876 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
878 *(dstBufferPtr + dstIndex + channel) = src[channel];
882 // Save leftover for next pixel in scan
883 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
885 oldLeft[channel] = left[channel];
889 // Go to bottom point of skew
894 i = static_cast<uint32_t>(yPos);
897 // If still in image bounds, put leftovers there
898 const uint32_t dstIndex = pixelSize * (i * dstWidth + column);
900 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
902 *(dstBufferPtr + dstIndex + channel) = oldLeft[channel];
910 // Clear below skewed line with background
911 const uint32_t dstIndex = pixelSize * (i * dstWidth + column);
913 for(uint32_t channel = 0u; channel < pixelSize; ++channel)
915 *(dstBufferPtr + dstIndex + channel) = 0u;
922 ImageDimensions CalculateDesiredDimensions(ImageDimensions rawDimensions, ImageDimensions requestedDimensions, FittingMode::Type fittingMode)
924 return CalculateDesiredDimensions(rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight(), fittingMode);
928 * @brief Apply cropping and padding for specified fitting mode.
930 * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
931 * based on the fitting mode.
933 * This will add vertical or horizontal borders if necessary.
934 * Crop the source image data vertically or horizontally if necessary.
935 * The aspect of the source image is preserved.
936 * If the source image is smaller than the desired size, the algorithm will modify the the newly created
937 * bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
938 * GPU scaling to be performed at render time giving the same result with less texture traversal.
940 * @param[in] bitmap The source pixel buffer to perform modifications on.
941 * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
942 * @param[in] fittingMode The fitting mode to use.
944 * @return A new bitmap with the padding and cropping required for fitting mode applied.
945 * If no modification is needed or possible, the passed in bitmap is returned.
947 Dali::Devel::PixelBuffer CropAndPadForFittingMode(Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode);
950 * @brief Adds horizontal or vertical borders to the source image.
952 * @param[in] targetPixels The destination image pointer to draw the borders on.
953 * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer.
954 * @param[in] targetDimensions The dimensions of the destination image.
955 * @param[in] padDimensions The columns and scanlines to pad with borders.
957 void AddBorders(PixelBuffer* targetPixels, const uint32_t bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions);
959 Dali::Devel::PixelBuffer ApplyAttributesToBitmap(Dali::Devel::PixelBuffer bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode)
963 // Calculate the desired box, accounting for a possible zero component:
964 const ImageDimensions desiredDimensions = CalculateDesiredDimensions(bitmap.GetWidth(), bitmap.GetHeight(), dimensions.GetWidth(), dimensions.GetHeight(), fittingMode);
966 // If a different size than the raw one has been requested, resize the image
967 // maximally using a repeated box filter without making it smaller than the
968 // requested size in either dimension:
969 bitmap = DownscaleBitmap(bitmap, desiredDimensions, fittingMode, samplingMode);
971 // Cut the bitmap according to the desired width and height so that the
972 // resulting bitmap has the same aspect ratio as the desired dimensions.
973 // Add crop and add borders if necessary depending on fitting mode.
976 bitmap = CropAndPadForFittingMode(bitmap, desiredDimensions, fittingMode);
983 Dali::Devel::PixelBuffer CropAndPadForFittingMode(Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode)
985 const uint32_t inputWidth = bitmap.GetWidth();
986 const uint32_t inputHeight = bitmap.GetHeight();
987 const uint32_t inputStride = bitmap.GetStride();
989 if(desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u)
991 DALI_LOG_WARNING("Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight());
993 else if(inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight())
995 // Calculate any padding or cropping that needs to be done based on the fitting mode.
996 // Note: If the desired size is larger than the original image, the desired size will be
997 // reduced while maintaining the aspect, in order to save unnecessary memory usage.
998 int scanlinesToCrop = 0;
999 int columnsToCrop = 0;
1001 CalculateBordersFromFittingMode(ImageDimensions(inputWidth, inputHeight), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop);
1003 uint32_t desiredWidth(desiredDimensions.GetWidth());
1004 uint32_t desiredHeight(desiredDimensions.GetHeight());
1006 // Action the changes by making a new bitmap with the central part of the loaded one if required.
1007 if(scanlinesToCrop != 0 || columnsToCrop != 0)
1009 // Split the adding and removing of scanlines and columns into separate variables,
1010 // so we can use one piece of generic code to action the changes.
1011 uint32_t scanlinesToPad = 0;
1012 uint32_t columnsToPad = 0;
1013 if(scanlinesToCrop < 0)
1015 scanlinesToPad = -scanlinesToCrop;
1016 scanlinesToCrop = 0;
1018 if(columnsToCrop < 0)
1020 columnsToPad = -columnsToCrop;
1024 // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
1025 if((desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE) || (desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE) ||
1026 (columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE) || (scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE))
1028 DALI_LOG_WARNING("Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight);
1032 DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_CROP_AND_PAD_BITMAP", [&](std::ostringstream& oss) {
1033 oss << "[origin:" << inputWidth << "x" << inputHeight << " ";
1034 oss << "desired:" << desiredWidth << "x" << desiredHeight << " ";
1035 oss << "fittingMode:" << fittingMode << "]";
1038 // Create new PixelBuffer with the desired size.
1039 const auto pixelFormat = bitmap.GetPixelFormat();
1041 auto croppedBitmap = Devel::PixelBuffer::New(desiredWidth, desiredHeight, pixelFormat);
1043 // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
1044 // The cropping is added to the source pointer, and the padding is added to the destination.
1045 const auto bytesPerPixel = Pixel::GetBytesPerPixel(pixelFormat);
1046 const PixelBuffer* const sourcePixels = bitmap.GetBuffer() + ((((scanlinesToCrop / 2) * inputStride) + (columnsToCrop / 2)) * bytesPerPixel);
1047 PixelBuffer* const targetPixels = croppedBitmap.GetBuffer();
1048 PixelBuffer* const targetPixelsActive = targetPixels + ((((scanlinesToPad / 2) * desiredWidth) + (columnsToPad / 2)) * bytesPerPixel);
1049 DALI_ASSERT_DEBUG(sourcePixels && targetPixels);
1051 // Copy the image data to the new bitmap.
1052 // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
1053 uint32_t outputSpan(desiredWidth * bytesPerPixel);
1054 if(columnsToCrop == 0 && columnsToPad == 0 && inputStride == inputWidth)
1056 memcpy(targetPixelsActive, sourcePixels, (desiredHeight - scanlinesToPad) * outputSpan);
1060 // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
1061 // Precalculate any constants to optimize the inner loop.
1062 const uint32_t inputSpan(inputStride * bytesPerPixel);
1063 const uint32_t copySpan((desiredWidth - columnsToPad) * bytesPerPixel);
1064 const uint32_t scanlinesToCopy(desiredHeight - scanlinesToPad);
1066 for(uint32_t y = 0; y < scanlinesToCopy; ++y)
1068 memcpy(&targetPixelsActive[y * outputSpan], &sourcePixels[y * inputSpan], copySpan);
1072 // Add vertical or horizontal borders to the final image (if required).
1073 desiredDimensions.SetWidth(desiredWidth);
1074 desiredDimensions.SetHeight(desiredHeight);
1075 AddBorders(croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions(columnsToPad, scanlinesToPad));
1076 // Overwrite the loaded bitmap with the cropped version
1077 bitmap = croppedBitmap;
1079 DALI_TRACE_END(gTraceFilter, "DALI_CROP_AND_PAD_BITMAP");
1086 void AddBorders(PixelBuffer* targetPixels, const uint32_t bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions)
1088 // Assign ints for faster access.
1089 uint32_t desiredWidth(targetDimensions.GetWidth());
1090 uint32_t desiredHeight(targetDimensions.GetHeight());
1091 uint32_t columnsToPad(padDimensions.GetWidth());
1092 uint32_t scanlinesToPad(padDimensions.GetHeight());
1093 uint32_t outputSpan(desiredWidth * bytesPerPixel);
1095 // Add letterboxing (symmetrical borders) if needed.
1096 if(scanlinesToPad > 0)
1098 // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
1099 memset(targetPixels, BORDER_FILL_VALUE, (scanlinesToPad / 2) * outputSpan);
1101 // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
1102 // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
1103 uint32_t bottomBorderHeight = scanlinesToPad - (scanlinesToPad / 2);
1106 memset(&targetPixels[(desiredHeight - bottomBorderHeight) * outputSpan], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan);
1108 else if(columnsToPad > 0)
1110 // Add a left and right border.
1112 // Pre-calculate span size outside of loop.
1113 uint32_t leftBorderSpanWidth((columnsToPad / 2) * bytesPerPixel);
1114 for(uint32_t y = 0; y < desiredHeight; ++y)
1116 memset(&targetPixels[y * outputSpan], BORDER_FILL_VALUE, leftBorderSpanWidth);
1120 // Pre-calculate the initial x offset as it is always the same for a small optimization.
1121 // We subtract columnsToPad/2 from columnsToPad so that we have the correct
1122 // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
1123 uint32_t rightBorderWidth = columnsToPad - (columnsToPad / 2);
1124 PixelBuffer* const destPixelsRightBorder(targetPixels + ((desiredWidth - rightBorderWidth) * bytesPerPixel));
1125 uint32_t rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
1127 for(uint32_t y = 0; y < desiredHeight; ++y)
1129 memset(&destPixelsRightBorder[y * outputSpan], BORDER_FILL_VALUE, rightBorderSpanWidth);
1134 Dali::Devel::PixelBuffer DownscaleBitmap(Dali::Devel::PixelBuffer bitmap,
1135 ImageDimensions desired,
1136 FittingMode::Type fittingMode,
1137 SamplingMode::Type samplingMode)
1139 // Source dimensions as loaded from resources (e.g. filesystem):
1140 auto bitmapWidth = bitmap.GetWidth();
1141 auto bitmapHeight = bitmap.GetHeight();
1142 auto bitmapStride = bitmap.GetStride();
1143 // Desired dimensions (the rectangle to fit the source image to):
1144 auto desiredWidth = desired.GetWidth();
1145 auto desiredHeight = desired.GetHeight();
1147 Dali::Devel::PixelBuffer outputBitmap{bitmap};
1149 // If a different size than the raw one has been requested, resize the image:
1151 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
1152 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)))
1154 DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_DOWNSCALE_BITMAP", [&](std::ostringstream& oss) {
1155 oss << "[origin:" << bitmapWidth << "x" << bitmapHeight << " ";
1156 oss << "desired:" << desiredWidth << "x" << desiredHeight << " ";
1157 oss << "fittingMode:" << fittingMode << " ";
1158 oss << "samplingMode:" << samplingMode << "]";
1160 auto pixelFormat = bitmap.GetPixelFormat();
1162 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
1163 uint32_t shrunkWidth = -1, shrunkHeight = -1, outStride = -1;
1164 DownscaleInPlacePow2(bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, bitmapStride, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight, outStride);
1166 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
1167 const ImageDimensions filteredDimensions = FitToScalingMode(ImageDimensions(desiredWidth, desiredHeight), ImageDimensions(shrunkWidth, shrunkHeight), fittingMode);
1168 const uint32_t filteredWidth = filteredDimensions.GetWidth();
1169 const uint32_t filteredHeight = filteredDimensions.GetHeight();
1171 // Run a filter to scale down the bitmap if it needs it:
1172 bool filtered = false;
1173 if(filteredWidth < shrunkWidth || filteredHeight < shrunkHeight)
1175 if(samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
1176 samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST)
1178 outputBitmap = Dali::Devel::PixelBuffer::New(filteredWidth, filteredHeight, pixelFormat);
1182 if(samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR)
1184 LinearSample(bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), outStride, pixelFormat, outputBitmap.GetBuffer(), filteredDimensions);
1188 PointSample(bitmap.GetBuffer(), shrunkWidth, shrunkHeight, outStride, pixelFormat, outputBitmap.GetBuffer(), filteredWidth, filteredHeight);
1194 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
1195 if(filtered == false && (shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight))
1197 // The buffer is downscaled and it is tightly packed. We don't need to set a stride.
1198 outputBitmap = MakePixelBuffer(bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight);
1200 DALI_TRACE_END_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_DOWNSCALE_BITMAP", [&](std::ostringstream& oss) {
1201 oss << "[origin:" << bitmapWidth << "x" << bitmapHeight << " ";
1202 oss << "desired:" << desiredWidth << "x" << desiredHeight << " ";
1203 oss << "final:" << outputBitmap.GetWidth() << "x" << outputBitmap.GetHeight() << "]";
1207 return outputBitmap;
1213 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
1214 * @param test Which combination of the two dimensions matter for terminating the filtering.
1215 * @param scaledWidth The width of the current downscaled image.
1216 * @param scaledHeight The height of the current downscaled image.
1217 * @param desiredWidth The target width for the downscaling.
1218 * @param desiredHeight The target height for the downscaling.
1220 bool ContinueScaling(BoxDimensionTest test, uint32_t scaledWidth, uint32_t scaledHeight, uint32_t desiredWidth, uint32_t desiredHeight)
1222 bool keepScaling = false;
1223 const uint32_t nextWidth = scaledWidth >> 1u;
1224 const uint32_t nextHeight = scaledHeight >> 1u;
1226 if(nextWidth >= 1u && nextHeight >= 1u)
1230 case BoxDimensionTestEither:
1232 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
1235 case BoxDimensionTestBoth:
1237 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
1240 case BoxDimensionTestX:
1242 keepScaling = nextWidth >= desiredWidth;
1245 case BoxDimensionTestY:
1247 keepScaling = nextHeight >= desiredHeight;
1257 * @brief A shared implementation of the overall iterative box filter
1258 * downscaling algorithm.
1260 * Specialise this for particular pixel formats by supplying the number of bytes
1261 * per pixel and two functions: one for averaging pairs of neighbouring pixels
1262 * on a single scanline, and a second for averaging pixels at corresponding
1263 * positions on different scanlines.
1266 int BYTES_PER_PIXEL,
1267 void (*HalveScanlineInPlace)(uint8_t* const pixels, const uint32_t width),
1268 void (*AverageScanlines)(const uint8_t* const scanline1, const uint8_t* const __restrict__ scanline2, uint8_t* const outputScanline, const uint32_t width)>
1269 void DownscaleInPlacePow2Generic(uint8_t* const pixels,
1270 const uint32_t inputWidth,
1271 const uint32_t inputHeight,
1272 const uint32_t inputStride,
1273 const uint32_t desiredWidth,
1274 const uint32_t desiredHeight,
1275 BoxDimensionTest dimensionTest,
1277 uint32_t& outHeight,
1278 uint32_t& outStride)
1284 ValidateScalingParameters(inputWidth, inputHeight, desiredWidth, desiredHeight);
1286 // Scale the image until it would be smaller than desired, stopping if the
1287 // resulting height or width would be less than 1:
1288 uint32_t scaledWidth = inputWidth, scaledHeight = inputHeight, stride = inputStride;
1289 while(ContinueScaling(dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight))
1291 const uint32_t lastWidth = scaledWidth;
1292 const uint32_t lastStride = stride;
1294 scaledHeight >>= 1u;
1295 stride = scaledWidth;
1297 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight);
1299 const uint32_t lastScanlinePair = scaledHeight - 1;
1301 // Scale pairs of scanlines until any spare one at the end is dropped:
1302 for(uint32_t y = 0; y <= lastScanlinePair; ++y)
1304 // Scale two scanlines horizontally:
1305 HalveScanlineInPlace(&pixels[y * 2 * lastStride * BYTES_PER_PIXEL], lastWidth);
1306 HalveScanlineInPlace(&pixels[(y * 2 + 1) * lastStride * BYTES_PER_PIXEL], lastWidth);
1308 // Scale vertical pairs of pixels while the last two scanlines are still warm in
1309 // the CPU cache(s):
1310 // Note, better access patterns for cache-coherence are possible for very large
1311 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
1312 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
1314 &pixels[y * 2 * lastStride * BYTES_PER_PIXEL],
1315 &pixels[(y * 2 + 1) * lastStride * BYTES_PER_PIXEL],
1316 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
1321 ///@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.
1322 outWidth = scaledWidth;
1323 outHeight = scaledHeight;
1329 void HalveScanlineInPlaceRGB888(uint8_t* const pixels, const uint32_t width)
1331 DebugAssertScanlineParameters(pixels, width);
1333 const uint32_t lastPair = EvenDown(width - 2);
1337 * for(uint32_t pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1339 * // Load all the byte pixel components we need:
1340 * const uint32_t c11 = pixels[pixel * 3];
1341 * const uint32_t c12 = pixels[pixel * 3 + 1];
1342 * const uint32_t c13 = pixels[pixel * 3 + 2];
1343 * const uint32_t c21 = pixels[pixel * 3 + 3];
1344 * const uint32_t c22 = pixels[pixel * 3 + 4];
1345 * const uint32_t c23 = pixels[pixel * 3 + 5];
1347 * // Save the averaged byte pixel components:
1348 * pixels[outPixel * 3] = static_cast<uint8_t>(AverageComponent(c11, c21));
1349 * pixels[outPixel * 3 + 1] = static_cast<uint8_t>(AverageComponent(c12, c22));
1350 * pixels[outPixel * 3 + 2] = static_cast<uint8_t>(AverageComponent(c13, c23));
1354 //@ToDo : Fix here if we found that collect 12 bytes == 3 uint32_t with 4 colors, and calculate in one-operation
1355 std::uint8_t* inPixelPtr = pixels;
1356 std::uint8_t* outPixelPtr = pixels;
1357 for(std::uint32_t scanedPixelCount = 0; scanedPixelCount <= lastPair; scanedPixelCount += 2)
1359 *(outPixelPtr + 0) = ((*(inPixelPtr + 0) ^ *(inPixelPtr + 3)) >> 1) + (*(inPixelPtr + 0) & *(inPixelPtr + 3));
1360 *(outPixelPtr + 1) = ((*(inPixelPtr + 1) ^ *(inPixelPtr + 4)) >> 1) + (*(inPixelPtr + 1) & *(inPixelPtr + 4));
1361 *(outPixelPtr + 2) = ((*(inPixelPtr + 2) ^ *(inPixelPtr + 5)) >> 1) + (*(inPixelPtr + 2) & *(inPixelPtr + 5));
1367 void HalveScanlineInPlaceRGBA8888(uint8_t* const pixels, const uint32_t width)
1369 DebugAssertScanlineParameters(pixels, width);
1370 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1372 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
1374 const uint32_t lastPair = EvenDown(width - 2);
1376 for(uint32_t pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1378 const uint32_t averaged = AveragePixelRGBA8888(alignedPixels[pixel], alignedPixels[pixel + 1]);
1379 alignedPixels[outPixel] = averaged;
1383 void HalveScanlineInPlaceRGB565(uint8_t* pixels, uint32_t width)
1385 DebugAssertScanlineParameters(pixels, width);
1386 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1388 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
1390 const uint32_t lastPair = EvenDown(width - 2);
1392 for(uint32_t pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1394 const uint16_t averaged = AveragePixelRGB565(alignedPixels[pixel], alignedPixels[pixel + 1]);
1395 alignedPixels[outPixel] = averaged;
1399 void HalveScanlineInPlace2Bytes(uint8_t* const pixels, const uint32_t width)
1401 DebugAssertScanlineParameters(pixels, width);
1403 const uint32_t lastPair = EvenDown(width - 2);
1405 for(uint32_t pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1409 * // Load all the byte pixel components we need:
1410 * const uint32_t c11 = pixels[pixel * 2];
1411 * const uint32_t c12 = pixels[pixel * 2 + 1];
1412 * const uint32_t c21 = pixels[pixel * 2 + 2];
1413 * const uint32_t c22 = pixels[pixel * 2 + 3];
1415 * // Save the averaged byte pixel components:
1416 * pixels[outPixel * 2] = static_cast<uint8_t>(AverageComponent(c11, c21));
1417 * pixels[outPixel * 2 + 1] = static_cast<uint8_t>(AverageComponent(c12, c22));
1420 // Note : We can assume that pixel is even number. So we can use | operation instead of + operation.
1421 pixels[(outPixel << 1)] = ((pixels[(pixel << 1)] ^ pixels[(pixel << 1) | 2]) >> 1) + (pixels[(pixel << 1)] & pixels[(pixel << 1) | 2]);
1422 pixels[(outPixel << 1) | 1] = ((pixels[(pixel << 1) | 1] ^ pixels[(pixel << 1) | 3]) >> 1) + (pixels[(pixel << 1) | 1] & pixels[(pixel << 1) | 3]);
1426 void HalveScanlineInPlace1Byte(uint8_t* const pixels, const uint32_t width)
1428 DebugAssertScanlineParameters(pixels, width);
1430 const uint32_t lastPair = EvenDown(width - 2);
1432 for(uint32_t pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1436 * // Load all the byte pixel components we need:
1437 * const uint32_t c1 = pixels[pixel];
1438 * const uint32_t c2 = pixels[pixel + 1];
1440 * // Save the averaged byte pixel component:
1441 * pixels[outPixel] = static_cast<uint8_t>(AverageComponent(c1, c2));
1444 // Note : We can assume that pixel is even number. So we can use | operation instead of + operation.
1445 pixels[outPixel] = ((pixels[pixel] ^ pixels[pixel | 1]) >> 1) + (pixels[pixel] & pixels[pixel | 1]);
1454 * @copydoc AverageScanlines1
1455 * @note This API average eight components in one operation.
1456 * @note Only possible if each scanline pointer's address aligned
1457 * It will give performance benifit.
1459 inline void AverageScanlinesWithMultipleComponents(
1460 const uint8_t* const scanline1,
1461 const uint8_t* const __restrict__ scanline2,
1462 uint8_t* const outputScanline,
1463 const uint32_t totalComponentCount)
1465 uint32_t component = 0;
1466 if(DALI_LIKELY(totalComponentCount >= 16))
1468 // Note reinsterpret_cast from uint8_t to uint64_t (or uint32_t) and read/write only allowed
1469 // If pointer of data is aligned well.
1470 // (to avoid SIGBUS)
1472 // To increase the percentage of optimized works, let we check pre-padding value of each pointer.
1473 auto scanline1Padding = (reinterpret_cast<std::ptrdiff_t>(scanline1) & (sizeof(std::uint64_t) - 1));
1474 auto scanline2Padding = (reinterpret_cast<std::ptrdiff_t>(scanline2) & (sizeof(std::uint64_t) - 1));
1475 auto outScanlinePadding = (reinterpret_cast<std::ptrdiff_t>(outputScanline) & (sizeof(std::uint64_t) - 1));
1476 if((scanline1Padding == scanline2Padding) && (scanline1Padding == outScanlinePadding))
1478 const auto padding = (sizeof(std::uint64_t) - scanline1Padding) & (sizeof(std::uint64_t) - 1);
1480 // Prepadding range calculate
1481 for(std::uint32_t i = 0; i < padding; ++i)
1483 const auto& c1 = scanline1[i];
1484 const auto& c2 = scanline2[i];
1485 outputScanline[i] = static_cast<std::uint8_t>(((c1 ^ c2) >> 1) + (c1 & c2));
1488 // Jump 8 components in one step
1489 const std::uint64_t* const scanline18Step = reinterpret_cast<const std::uint64_t* const>(scanline1 + padding);
1490 const std::uint64_t* const scanline28Step = reinterpret_cast<const std::uint64_t* const>(scanline2 + padding);
1491 std::uint64_t* const output8step = reinterpret_cast<std::uint64_t* const>(outputScanline + padding);
1493 const std::uint32_t totalStepCount = (totalComponentCount) >> 3;
1494 component = (totalStepCount << 3) + padding;
1496 // and for each step, calculate average of 8 bytes.
1497 for(std::uint32_t i = 0; i < totalStepCount; ++i)
1499 const auto& c1 = *(scanline18Step + i);
1500 const auto& c2 = *(scanline28Step + i);
1501 *(output8step + i) = static_cast<std::uint64_t>((((c1 ^ c2) & 0xfefefefefefefefeull) >> 1) + (c1 & c2));
1504 else if(((scanline1Padding & (sizeof(std::uint32_t) - 1)) == (scanline2Padding & (sizeof(std::uint32_t) - 1))) &&
1505 ((scanline1Padding & (sizeof(std::uint32_t) - 1)) == (outScanlinePadding & (sizeof(std::uint32_t) - 1))))
1507 const auto padding = (sizeof(std::uint64_t) - scanline1Padding) & (sizeof(std::uint32_t) - 1);
1509 // Prepadding range calculate
1510 for(std::uint32_t i = 0; i < padding; ++i)
1512 const auto& c1 = scanline1[i];
1513 const auto& c2 = scanline2[i];
1514 outputScanline[i] = static_cast<std::uint8_t>(((c1 ^ c2) >> 1) + (c1 & c2));
1517 // Jump 4 components in one step
1518 const std::uint32_t* const scanline14Step = reinterpret_cast<const std::uint32_t* const>(scanline1 + padding);
1519 const std::uint32_t* const scanline24Step = reinterpret_cast<const std::uint32_t* const>(scanline2 + padding);
1520 std::uint32_t* const output4step = reinterpret_cast<std::uint32_t* const>(outputScanline + padding);
1522 const std::uint32_t totalStepCount = (totalComponentCount) >> 2;
1523 component = (totalStepCount << 2) + padding;
1525 // and for each step, calculate average of 4 bytes.
1526 for(std::uint32_t i = 0; i < totalStepCount; ++i)
1528 const auto& c1 = *(scanline14Step + i);
1529 const auto& c2 = *(scanline24Step + i);
1530 *(output4step + i) = static_cast<std::uint32_t>((((c1 ^ c2) & 0xfefefefeu) >> 1) + (c1 & c2));
1534 // remaining components calculate
1535 for(; component < totalComponentCount; ++component)
1537 const auto& c1 = scanline1[component];
1538 const auto& c2 = scanline2[component];
1539 outputScanline[component] = static_cast<std::uint8_t>(((c1 ^ c2) >> 1) + (c1 & c2));
1545 void AverageScanlines1(const uint8_t* const scanline1,
1546 const uint8_t* const __restrict__ scanline2,
1547 uint8_t* const outputScanline,
1548 const uint32_t width)
1550 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width);
1554 * for(uint32_t component = 0; component < width; ++component)
1556 * outputScanline[component] = static_cast<uint8_t>(AverageComponent(scanline1[component], scanline2[component]));
1560 AverageScanlinesWithMultipleComponents(scanline1, scanline2, outputScanline, width);
1563 void AverageScanlines2(const uint8_t* const scanline1,
1564 const uint8_t* const __restrict__ scanline2,
1565 uint8_t* const outputScanline,
1566 const uint32_t width)
1568 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 2);
1572 * for(uint32_t component = 0; component < width * 2; ++component)
1574 * outputScanline[component] = static_cast<uint8_t>(AverageComponent(scanline1[component], scanline2[component]));
1578 AverageScanlinesWithMultipleComponents(scanline1, scanline2, outputScanline, width * 2);
1581 void AverageScanlines3(const uint8_t* const scanline1,
1582 const uint8_t* const __restrict__ scanline2,
1583 uint8_t* const outputScanline,
1584 const uint32_t width)
1586 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 3);
1590 * for(uint32_t component = 0; component < width * 3; ++component)
1592 * outputScanline[component] = static_cast<uint8_t>(AverageComponent(scanline1[component], scanline2[component]));
1596 AverageScanlinesWithMultipleComponents(scanline1, scanline2, outputScanline, width * 3);
1599 void AverageScanlinesRGBA8888(const uint8_t* const scanline1,
1600 const uint8_t* const __restrict__ scanline2,
1601 uint8_t* const outputScanline,
1602 const uint32_t width)
1604 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 4);
1605 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1606 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1607 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1611 * const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1612 * const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1613 * uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1615 * for(uint32_t pixel = 0; pixel < width; ++pixel)
1617 * alignedOutput[pixel] = AveragePixelRGBA8888(alignedScanline1[pixel], alignedScanline2[pixel]);
1622 AverageScanlinesWithMultipleComponents(scanline1, scanline2, outputScanline, width * 4u);
1625 void AverageScanlinesRGB565(const uint8_t* const scanline1,
1626 const uint8_t* const __restrict__ scanline2,
1627 uint8_t* const outputScanline,
1628 const uint32_t width)
1630 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 2);
1631 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1632 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1633 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1635 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1636 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1637 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1639 for(uint32_t pixel = 0; pixel < width; ++pixel)
1641 alignedOutput[pixel] = AveragePixelRGB565(alignedScanline1[pixel], alignedScanline2[pixel]);
1645 /// Dispatch to pixel format appropriate box filter downscaling functions.
1646 void DownscaleInPlacePow2(uint8_t* const pixels,
1647 Pixel::Format pixelFormat,
1648 uint32_t inputWidth,
1649 uint32_t inputHeight,
1650 uint32_t inputStride,
1651 uint32_t desiredWidth,
1652 uint32_t desiredHeight,
1653 FittingMode::Type fittingMode,
1654 SamplingMode::Type samplingMode,
1656 uint32_t& outHeight,
1657 uint32_t& outStride)
1659 outWidth = inputWidth;
1660 outHeight = inputHeight;
1661 outStride = inputStride;
1662 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1663 if(samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR)
1665 // Check the pixel format is one that is supported:
1666 if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::CHROMINANCE_U || pixelFormat == Pixel::CHROMINANCE_V)
1668 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode(fittingMode);
1672 case Pixel::RGBA8888:
1674 Internal::Platform::DownscaleInPlacePow2RGBA8888(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1679 Internal::Platform::DownscaleInPlacePow2RGB888(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1684 Internal::Platform::DownscaleInPlacePow2RGB565(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1689 Internal::Platform::DownscaleInPlacePow2ComponentPair(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1694 case Pixel::CHROMINANCE_U:
1695 case Pixel::CHROMINANCE_V:
1697 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1702 DALI_ASSERT_DEBUG(false && "Inner branch conditions don't match outer branch.");
1709 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", uint32_t(pixelFormat));
1713 void DownscaleInPlacePow2RGB888(uint8_t* pixels,
1714 uint32_t inputWidth,
1715 uint32_t inputHeight,
1716 uint32_t inputStride,
1717 uint32_t desiredWidth,
1718 uint32_t desiredHeight,
1719 BoxDimensionTest dimensionTest,
1721 uint32_t& outHeight,
1722 uint32_t& outStride)
1724 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1727 void DownscaleInPlacePow2RGBA8888(uint8_t* pixels,
1728 uint32_t inputWidth,
1729 uint32_t inputHeight,
1730 uint32_t inputStride,
1731 uint32_t desiredWidth,
1732 uint32_t desiredHeight,
1733 BoxDimensionTest dimensionTest,
1735 uint32_t& outHeight,
1736 uint32_t& outStride)
1738 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1739 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1742 void DownscaleInPlacePow2RGB565(uint8_t* pixels,
1743 uint32_t inputWidth,
1744 uint32_t inputHeight,
1745 uint32_t inputStride,
1746 uint32_t desiredWidth,
1747 uint32_t desiredHeight,
1748 BoxDimensionTest dimensionTest,
1750 uint32_t& outHeight,
1751 uint32_t& outStride)
1753 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1757 * @copydoc DownscaleInPlacePow2RGB888
1759 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1761 void DownscaleInPlacePow2ComponentPair(uint8_t* pixels,
1762 uint32_t inputWidth,
1763 uint32_t inputHeight,
1764 uint32_t inputStride,
1765 uint32_t desiredWidth,
1766 uint32_t desiredHeight,
1767 BoxDimensionTest dimensionTest,
1769 uint32_t& outHeight,
1770 uint32_t& outStride)
1772 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1775 void DownscaleInPlacePow2SingleBytePerPixel(uint8_t* pixels,
1776 uint32_t inputWidth,
1777 uint32_t inputHeight,
1778 uint32_t inputStride,
1779 uint32_t desiredWidth,
1780 uint32_t desiredHeight,
1781 BoxDimensionTest dimensionTest,
1783 uint32_t& outHeight,
1784 uint32_t& outStride)
1786 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>(pixels, inputWidth, inputHeight, inputStride, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight, outStride);
1789 // Point sampling group below
1794 * @brief Point sample an image to a new resolution (like GL_NEAREST).
1796 * Template is used purely as a type-safe code generator in this one
1797 * compilation unit. Generated code is inlined into type-specific wrapper
1798 * functions below which are exported to rest of module.
1800 template<typename PIXEL>
1801 inline void PointSampleAddressablePixels(const uint8_t* inPixels,
1802 uint32_t inputWidth,
1803 uint32_t inputHeight,
1804 uint32_t inputStride,
1806 uint32_t desiredWidth,
1807 uint32_t desiredHeight)
1809 DALI_ASSERT_DEBUG(((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1810 outPixels >= inPixels + inputStride * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1811 "The input and output buffers must not overlap for an upscaling.");
1812 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, ...).");
1813 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, ...).");
1815 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1819 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1820 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1821 const uint32_t deltaX = (inputWidth << 16u) / desiredWidth;
1822 const uint32_t deltaY = (inputHeight << 16u) / desiredHeight;
1825 for(uint32_t outY = 0; outY < desiredHeight; ++outY)
1827 // Round fixed point y coordinate to nearest integer:
1828 const uint32_t integerY = (inY + (1u << 15u)) >> 16u;
1829 const PIXEL* const inScanline = &inAligned[inputStride * integerY];
1830 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1832 DALI_ASSERT_DEBUG(integerY < inputHeight);
1833 DALI_ASSERT_DEBUG(reinterpret_cast<const uint8_t*>(inScanline) < (inPixels + inputStride * inputHeight * sizeof(PIXEL)));
1834 DALI_ASSERT_DEBUG(reinterpret_cast<uint8_t*>(outScanline) < (outPixels + desiredWidth * desiredHeight * sizeof(PIXEL)));
1837 for(uint32_t outX = 0; outX < desiredWidth; ++outX)
1839 // Round the fixed-point x coordinate to an integer:
1840 const uint32_t integerX = (inX + (1u << 15u)) >> 16u;
1841 const PIXEL* const inPixelAddress = &inScanline[integerX];
1842 const PIXEL pixel = *inPixelAddress;
1843 outScanline[outX] = pixel;
1853 void PointSample4BPP(const uint8_t* inPixels,
1854 uint32_t inputWidth,
1855 uint32_t inputHeight,
1856 uint32_t inputStride,
1858 uint32_t desiredWidth,
1859 uint32_t desiredHeight)
1861 PointSampleAddressablePixels<uint32_t>(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1865 void PointSample2BPP(const uint8_t* inPixels,
1866 uint32_t inputWidth,
1867 uint32_t inputHeight,
1868 uint32_t inputStride,
1870 uint32_t desiredWidth,
1871 uint32_t desiredHeight)
1873 PointSampleAddressablePixels<uint16_t>(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1877 void PointSample1BPP(const uint8_t* inPixels,
1878 uint32_t inputWidth,
1879 uint32_t inputHeight,
1880 uint32_t inputStride,
1882 uint32_t desiredWidth,
1883 uint32_t desiredHeight)
1885 PointSampleAddressablePixels<uint8_t>(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1889 * RGB888 is a special case as its pixels are not aligned addressable units.
1891 void PointSample3BPP(const uint8_t* inPixels,
1892 uint32_t inputWidth,
1893 uint32_t inputHeight,
1894 uint32_t inputStride,
1896 uint32_t desiredWidth,
1897 uint32_t desiredHeight)
1899 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1903 const uint32_t BYTES_PER_PIXEL = 3;
1905 // Generate fixed-point 16.16 deltas in input image coordinates:
1906 const uint32_t deltaX = (inputWidth << 16u) / desiredWidth;
1907 const uint32_t deltaY = (inputHeight << 16u) / desiredHeight;
1909 // Step through output image in whole integer pixel steps while tracking the
1910 // corresponding locations in the input image using 16.16 fixed-point
1912 uint32_t inY = 0; //< 16.16 fixed-point input image y-coord.
1913 for(uint32_t outY = 0; outY < desiredHeight; ++outY)
1915 const uint32_t integerY = (inY + (1u << 15u)) >> 16u;
1916 const uint8_t* const inScanline = &inPixels[inputStride * integerY * BYTES_PER_PIXEL];
1917 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1918 uint32_t inX = 0; //< 16.16 fixed-point input image x-coord.
1920 for(uint32_t outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL)
1922 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1923 const uint32_t integerX = (inX + (1u << 15u)) >> 16u;
1924 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1926 // Issue loads for all pixel color components up-front:
1927 const uint32_t c0 = inPixelAddress[0];
1928 const uint32_t c1 = inPixelAddress[1];
1929 const uint32_t c2 = inPixelAddress[2];
1930 ///@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.
1932 // Output the pixel components:
1933 outScanline[outX] = static_cast<uint8_t>(c0);
1934 outScanline[outX + 1] = static_cast<uint8_t>(c1);
1935 outScanline[outX + 2] = static_cast<uint8_t>(c2);
1937 // Increment the fixed-point input coordinate:
1945 // Dispatch to a format-appropriate point sampling function:
1946 void PointSample(const uint8_t* inPixels,
1947 uint32_t inputWidth,
1948 uint32_t inputHeight,
1949 uint32_t inputStride,
1950 Pixel::Format pixelFormat,
1952 uint32_t desiredWidth,
1953 uint32_t desiredHeight)
1955 // Check the pixel format is one that is supported:
1956 if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::CHROMINANCE_U || pixelFormat == Pixel::CHROMINANCE_V)
1962 PointSample3BPP(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1965 case Pixel::RGBA8888:
1967 PointSample4BPP(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1973 PointSample2BPP(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1978 case Pixel::CHROMINANCE_U:
1979 case Pixel::CHROMINANCE_V:
1981 PointSample1BPP(inPixels, inputWidth, inputHeight, inputStride, outPixels, desiredWidth, desiredHeight);
1986 DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
1992 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", uint32_t(pixelFormat));
1996 // Linear sampling group below
2000 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
2001 inline uint8_t BilinearFilter1BPPByte(uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, uint32_t fractBlendHorizontal, uint32_t fractBlendVertical)
2003 return static_cast<uint8_t>(BilinearFilter1Component(tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical));
2006 /** @copydoc BilinearFilter1BPPByte */
2007 inline Pixel2Bytes BilinearFilter2Bytes(Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, uint32_t fractBlendHorizontal, uint32_t fractBlendVertical)
2010 pixel.l = static_cast<uint8_t>(BilinearFilter1Component(tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical));
2011 pixel.a = static_cast<uint8_t>(BilinearFilter1Component(tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical));
2015 /** @copydoc BilinearFilter1BPPByte */
2016 inline Pixel3Bytes BilinearFilterRGB888(Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, uint32_t fractBlendHorizontal, uint32_t fractBlendVertical)
2019 pixel.r = static_cast<uint8_t>(BilinearFilter1Component(tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical));
2020 pixel.g = static_cast<uint8_t>(BilinearFilter1Component(tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical));
2021 pixel.b = static_cast<uint8_t>(BilinearFilter1Component(tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical));
2025 /** @copydoc BilinearFilter1BPPByte */
2026 inline PixelRGB565 BilinearFilterRGB565(PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, uint32_t fractBlendHorizontal, uint32_t fractBlendVertical)
2028 const PixelRGB565 pixel = static_cast<PixelRGB565>((BilinearFilter1Component(tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical) << 11u) +
2029 (BilinearFilter1Component((tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical) << 5u) +
2030 BilinearFilter1Component(tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical));
2034 /** @copydoc BilinearFilter1BPPByte */
2035 inline Pixel4Bytes BilinearFilter4Bytes(Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, uint32_t fractBlendHorizontal, uint32_t fractBlendVertical)
2038 pixel.r = static_cast<uint8_t>(BilinearFilter1Component(tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical));
2039 pixel.g = static_cast<uint8_t>(BilinearFilter1Component(tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical));
2040 pixel.b = static_cast<uint8_t>(BilinearFilter1Component(tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical));
2041 pixel.a = static_cast<uint8_t>(BilinearFilter1Component(tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical));
2046 * @brief Generic version of bilinear sampling image resize function.
2047 * @note Limited to one compilation unit and exposed through type-specific
2048 * wrapper functions below.
2052 PIXEL (*BilinearFilter)(PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, uint32_t fractBlendHorizontal, uint32_t fractBlendVertical),
2053 bool DEBUG_ASSERT_ALIGNMENT>
2054 inline void LinearSampleGeneric(const uint8_t* __restrict__ inPixels,
2055 ImageDimensions inputDimensions,
2056 uint32_t inputStride,
2057 uint8_t* __restrict__ outPixels,
2058 ImageDimensions desiredDimensions)
2060 const uint32_t inputWidth = inputDimensions.GetWidth();
2061 const uint32_t inputHeight = inputDimensions.GetHeight();
2062 const uint32_t desiredWidth = desiredDimensions.GetWidth();
2063 const uint32_t desiredHeight = desiredDimensions.GetHeight();
2065 DALI_ASSERT_DEBUG(((outPixels >= inPixels + inputStride * inputHeight * sizeof(PIXEL)) ||
2066 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
2067 "Input and output buffers cannot overlap.");
2068 if(DEBUG_ASSERT_ALIGNMENT)
2070 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, ...).");
2071 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, ...).");
2074 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
2078 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
2079 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
2080 const uint32_t deltaX = (inputWidth << 16u) / desiredWidth;
2081 const uint32_t deltaY = (inputHeight << 16u) / desiredHeight;
2084 for(uint32_t outY = 0; outY < desiredHeight; ++outY)
2086 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
2088 // Find the two scanlines to blend and the weight to blend with:
2089 const uint32_t integerY1 = inY >> 16u;
2090 const uint32_t integerY2 = integerY1 + 1 >= inputHeight ? integerY1 : integerY1 + 1;
2091 const uint32_t inputYWeight = inY & 65535u;
2093 DALI_ASSERT_DEBUG(integerY1 < inputHeight);
2094 DALI_ASSERT_DEBUG(integerY2 < inputHeight);
2096 const PIXEL* const inScanline1 = &inAligned[inputStride * integerY1];
2097 const PIXEL* const inScanline2 = &inAligned[inputStride * integerY2];
2100 for(uint32_t outX = 0; outX < desiredWidth; ++outX)
2102 // Work out the two pixel scanline offsets for this cluster of four samples:
2103 const uint32_t integerX1 = inX >> 16u;
2104 const uint32_t integerX2 = integerX1 + 1 >= inputWidth ? integerX1 : integerX1 + 1;
2106 // Execute the loads:
2107 const PIXEL pixel1 = inScanline1[integerX1];
2108 const PIXEL pixel2 = inScanline2[integerX1];
2109 const PIXEL pixel3 = inScanline1[integerX2];
2110 const PIXEL pixel4 = inScanline2[integerX2];
2111 ///@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.
2113 // Weighted bilinear filter:
2114 const uint32_t inputXWeight = inX & 65535u;
2115 outScanline[outX] = BilinearFilter(pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight);
2125 // Format-specific linear scaling instantiations:
2127 void LinearSample1BPP(const uint8_t* __restrict__ inPixels,
2128 ImageDimensions inputDimensions,
2129 uint32_t inputStride,
2130 uint8_t* __restrict__ outPixels,
2131 ImageDimensions desiredDimensions)
2133 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2136 void LinearSample2BPP(const uint8_t* __restrict__ inPixels,
2137 ImageDimensions inputDimensions,
2138 uint32_t inputStride,
2139 uint8_t* __restrict__ outPixels,
2140 ImageDimensions desiredDimensions)
2142 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2145 void LinearSampleRGB565(const uint8_t* __restrict__ inPixels,
2146 ImageDimensions inputDimensions,
2147 uint32_t inputStride,
2148 uint8_t* __restrict__ outPixels,
2149 ImageDimensions desiredDimensions)
2151 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2154 void LinearSample3BPP(const uint8_t* __restrict__ inPixels,
2155 ImageDimensions inputDimensions,
2156 uint32_t inputStride,
2157 uint8_t* __restrict__ outPixels,
2158 ImageDimensions desiredDimensions)
2160 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2163 void LinearSample4BPP(const uint8_t* __restrict__ inPixels,
2164 ImageDimensions inputDimensions,
2165 uint32_t inputStride,
2166 uint8_t* __restrict__ outPixels,
2167 ImageDimensions desiredDimensions)
2169 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
2172 // Dispatch to a format-appropriate linear sampling function:
2173 void LinearSample(const uint8_t* __restrict__ inPixels,
2174 ImageDimensions inDimensions,
2176 Pixel::Format pixelFormat,
2177 uint8_t* __restrict__ outPixels,
2178 ImageDimensions outDimensions)
2180 // Check the pixel format is one that is supported:
2181 if(pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::CHROMINANCE_U || pixelFormat == Pixel::CHROMINANCE_V)
2187 LinearSample3BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2190 case Pixel::RGBA8888:
2192 LinearSample4BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2197 case Pixel::CHROMINANCE_U:
2198 case Pixel::CHROMINANCE_V:
2200 LinearSample1BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2205 LinearSample2BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2210 LinearSampleRGB565(inPixels, inDimensions, inStride, outPixels, outDimensions);
2215 DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
2221 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", uint32_t(pixelFormat));
2225 void Resample(const uint8_t* __restrict__ inPixels,
2226 ImageDimensions inputDimensions,
2227 uint32_t inputStride,
2228 uint8_t* __restrict__ outPixels,
2229 ImageDimensions desiredDimensions,
2230 Resampler::Filter filterType,
2234 // Got from the test.cpp of the ImageResampler lib.
2235 const float ONE_DIV_255 = 1.0f / 255.0f;
2236 const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
2237 const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
2238 const int ALPHA_CHANNEL = hasAlpha ? (numChannels - 1) : 0;
2240 static bool loadColorSpaces = true;
2241 static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
2242 static uint8_t linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
2244 if(loadColorSpaces) // Only create the color space conversions on the first execution
2246 loadColorSpaces = false;
2248 for(int i = 0; i <= MAX_UNSIGNED_CHAR; ++i)
2250 srgbToLinear[i] = pow(static_cast<float>(i) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA);
2253 const float invLinearToSrgbTableSize = 1.0f / static_cast<float>(LINEAR_TO_SRGB_TABLE_SIZE);
2254 const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
2256 for(int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i)
2258 int k = static_cast<int>(255.0f * pow(static_cast<float>(i) * invLinearToSrgbTableSize, invSourceGamma) + 0.5f);
2263 else if(k > MAX_UNSIGNED_CHAR)
2265 k = MAX_UNSIGNED_CHAR;
2267 linearToSrgb[i] = static_cast<uint8_t>(k);
2271 std::vector<Resampler*> resamplers(numChannels);
2272 std::vector<Vector<float>> samples(numChannels);
2274 const int srcWidth = inputDimensions.GetWidth();
2275 const int srcHeight = inputDimensions.GetHeight();
2276 const int dstWidth = desiredDimensions.GetWidth();
2277 const int dstHeight = desiredDimensions.GetHeight();
2279 // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
2280 // used for the other components (a memory and slight cache efficiency optimization).
2281 resamplers[0] = new Resampler(srcWidth,
2285 Resampler::BOUNDARY_CLAMP,
2286 0.0f, // sample_low,
2287 1.0f, // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
2288 filterType, // The type of filter.
2290 NULL, // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
2291 FILTER_SCALE, // src_x_ofs,
2292 FILTER_SCALE); // src_y_ofs. Offset input image by specified amount (fractional values okay).
2293 samples[0].ResizeUninitialized(srcWidth);
2294 for(int i = 1; i < numChannels; ++i)
2296 resamplers[i] = new Resampler(srcWidth,
2300 Resampler::BOUNDARY_CLAMP,
2304 resamplers[0]->get_clist_x(),
2305 resamplers[0]->get_clist_y(),
2308 samples[i].ResizeUninitialized(srcWidth);
2311 const int srcPitch = inputStride * numChannels;
2312 const int dstPitch = dstWidth * numChannels;
2315 for(int srcY = 0; srcY < srcHeight; ++srcY)
2317 const uint8_t* pSrc = &inPixels[srcY * srcPitch];
2319 for(int x = 0; x < srcWidth; ++x)
2321 for(int c = 0; c < numChannels; ++c)
2323 if(c == ALPHA_CHANNEL && hasAlpha)
2325 samples[c][x] = *pSrc++ * ONE_DIV_255;
2329 samples[c][x] = srgbToLinear[*pSrc++];
2334 for(int c = 0; c < numChannels; ++c)
2336 if(!resamplers[c]->put_line(&samples[c][0]))
2338 DALI_ASSERT_DEBUG(!"Out of memory");
2345 for(compIndex = 0; compIndex < numChannels; ++compIndex)
2347 const float* pOutputSamples = resamplers[compIndex]->get_line();
2353 const bool isAlphaChannel = (compIndex == ALPHA_CHANNEL && hasAlpha);
2354 DALI_ASSERT_DEBUG(dstY < dstHeight);
2355 uint8_t* pDst = &outPixels[dstY * dstPitch + compIndex];
2357 for(int x = 0; x < dstWidth; ++x)
2361 int c = static_cast<int>(255.0f * pOutputSamples[x] + 0.5f);
2366 else if(c > MAX_UNSIGNED_CHAR)
2368 c = MAX_UNSIGNED_CHAR;
2370 *pDst = static_cast<uint8_t>(c);
2374 int j = static_cast<int>(LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f);
2379 else if(j >= LINEAR_TO_SRGB_TABLE_SIZE)
2381 j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
2383 *pDst = linearToSrgb[j];
2386 pDst += numChannels;
2389 if(compIndex < numChannels)
2398 // Delete the resamplers.
2399 for(int i = 0; i < numChannels; ++i)
2401 delete resamplers[i];
2405 void LanczosSample4BPP(const uint8_t* __restrict__ inPixels,
2406 ImageDimensions inputDimensions,
2407 uint32_t inputStride,
2408 uint8_t* __restrict__ outPixels,
2409 ImageDimensions desiredDimensions)
2411 Resample(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true);
2414 void LanczosSample1BPP(const uint8_t* __restrict__ inPixels,
2415 ImageDimensions inputDimensions,
2416 uint32_t inputStride,
2417 uint8_t* __restrict__ outPixels,
2418 ImageDimensions desiredDimensions)
2421 Resample(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false);
2424 // Dispatch to a format-appropriate third-party resampling function:
2425 void LanczosSample(const uint8_t* __restrict__ inPixels,
2426 ImageDimensions inDimensions,
2428 Pixel::Format pixelFormat,
2429 uint8_t* __restrict__ outPixels,
2430 ImageDimensions outDimensions)
2432 // Check the pixel format is one that is supported:
2433 if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::BGRA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
2437 case Pixel::RGBA8888:
2438 case Pixel::BGRA8888:
2440 LanczosSample4BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2446 LanczosSample1BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
2451 DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
2457 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not lanczos sampled: unsupported pixel format: %u.\n", static_cast<uint32_t>(pixelFormat));
2461 void RotateByShear(const uint8_t* const pixelsIn,
2467 uint8_t*& pixelsOut,
2469 uint32_t& heightOut)
2471 // @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
2473 // Do first the fast rotations to transform the angle into a (-45..45] range.
2475 bool fastRotationPerformed = false;
2476 if((radians > Math::PI_4) && (radians <= RAD_135))
2478 // Angle in (45.0 .. 135.0]
2479 // Rotate image by 90 degrees into temporary image,
2480 // so it requires only an extra rotation angle
2481 // of -45.0 .. +45.0 to complete rotation.
2482 fastRotationPerformed = Rotate90(pixelsIn,
2491 if(!fastRotationPerformed)
2493 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2494 // The fast rotation failed.
2498 radians -= Math::PI_2;
2500 else if((radians > RAD_135) && (radians <= RAD_225))
2502 // Angle in (135.0 .. 225.0]
2503 // Rotate image by 180 degrees into temporary image,
2504 // so it requires only an extra rotation angle
2505 // of -45.0 .. +45.0 to complete rotation.
2507 fastRotationPerformed = Rotate180(pixelsIn,
2514 if(!fastRotationPerformed)
2516 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2517 // The fast rotation failed.
2521 radians -= Math::PI;
2523 heightOut = heightIn;
2525 else if((radians > RAD_225) && (radians <= RAD_315))
2527 // Angle in (225.0 .. 315.0]
2528 // Rotate image by 270 degrees into temporary image,
2529 // so it requires only an extra rotation angle
2530 // of -45.0 .. +45.0 to complete rotation.
2532 fastRotationPerformed = Rotate270(pixelsIn,
2541 if(!fastRotationPerformed)
2543 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2544 // The fast rotation failed.
2551 if(fabs(radians) < Dali::Math::MACHINE_EPSILON_10)
2553 // Nothing else to do if the angle is zero.
2554 // The rotation angle was 90, 180 or 270.
2556 // @note Allocated memory by 'Fast Rotations', if any, has to be freed by the called to this function.
2560 const uint8_t* const firstHorizontalSkewPixelsIn = fastRotationPerformed ? pixelsOut : pixelsIn;
2561 std::unique_ptr<uint8_t, void (*)(void*)> tmpPixelsInPtr((fastRotationPerformed ? pixelsOut : nullptr), free);
2563 uint32_t stride = fastRotationPerformed ? widthOut : strideIn;
2565 // Reset the input/output
2567 heightIn = heightOut;
2568 pixelsOut = nullptr;
2570 const float angleSinus = sin(radians);
2571 const float angleCosinus = cos(radians);
2572 const float angleTangent = tan(0.5f * radians);
2574 ///////////////////////////////////////
2575 // Perform 1st shear (horizontal)
2576 ///////////////////////////////////////
2578 // Calculate first shear (horizontal) destination image dimensions
2580 widthOut = widthIn + static_cast<uint32_t>(fabs(angleTangent) * static_cast<float>(heightIn));
2581 heightOut = heightIn;
2583 // Allocate the buffer for the 1st shear
2584 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2586 if(nullptr == pixelsOut)
2588 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x %u\n", widthOut, heightOut, pixelSize);
2592 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2594 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Fast rotations'.
2595 // Nothing else to do if the memory allocation fails.
2599 for(uint32_t y = 0u; y < heightOut; ++y)
2601 const float shear = angleTangent * ((angleTangent >= 0.f) ? (0.5f + static_cast<float>(y)) : (0.5f + static_cast<float>(y) - static_cast<float>(heightOut)));
2603 const int intShear = static_cast<int>(floor(shear));
2604 HorizontalSkew(firstHorizontalSkewPixelsIn, widthIn, stride, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>(intShear));
2607 // Reset the 'pixel in' pointer with the output of the 'First Horizontal Skew' and free the memory allocated by the 'Fast Rotations'.
2608 tmpPixelsInPtr.reset(pixelsOut);
2609 uint32_t tmpWidthIn = widthOut;
2610 uint32_t tmpHeightIn = heightOut;
2612 // Reset the input/output
2613 pixelsOut = nullptr;
2615 ///////////////////////////////////////
2616 // Perform 2nd shear (vertical)
2617 ///////////////////////////////////////
2619 // Calc 2nd shear (vertical) destination image dimensions
2620 heightOut = static_cast<uint32_t>(static_cast<float>(widthIn) * fabs(angleSinus) + static_cast<float>(heightIn) * angleCosinus);
2622 // Allocate the buffer for the 2nd shear
2623 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2625 if(nullptr == pixelsOut)
2627 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x %u\n", widthOut, heightOut, pixelSize);
2631 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2632 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'First Horizontal Skew'.
2633 // Nothing else to do if the memory allocation fails.
2637 // Variable skew offset
2638 float offset = angleSinus * ((angleSinus > 0.f) ? static_cast<float>(widthIn - 1u) : -(static_cast<float>(widthIn) - static_cast<float>(widthOut)));
2640 uint32_t column = 0u;
2641 for(column = 0u; column < widthOut; ++column, offset -= angleSinus)
2643 const int32_t shear = static_cast<int32_t>(floor(offset));
2644 VerticalSkew(tmpPixelsInPtr.get(), tmpWidthIn, tmpHeightIn, tmpWidthIn, pixelSize, pixelsOut, widthOut, heightOut, column, shear, offset - static_cast<float>(shear));
2646 // Reset the 'pixel in' pointer with the output of the 'Vertical Skew' and free the memory allocated by the 'First Horizontal Skew'.
2647 // Reset the input/output
2648 tmpPixelsInPtr.reset(pixelsOut);
2649 tmpWidthIn = widthOut;
2650 tmpHeightIn = heightOut;
2651 pixelsOut = nullptr;
2653 ///////////////////////////////////////
2654 // Perform 3rd shear (horizontal)
2655 ///////////////////////////////////////
2657 // Calc 3rd shear (horizontal) destination image dimensions
2658 widthOut = static_cast<uint32_t>(static_cast<float>(heightIn) * fabs(angleSinus) + static_cast<float>(widthIn) * angleCosinus) + 1u;
2660 // Allocate the buffer for the 3rd shear
2661 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2663 if(nullptr == pixelsOut)
2665 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x %u\n", widthOut, heightOut, pixelSize);
2669 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2670 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2671 // Nothing else to do if the memory allocation fails.
2675 offset = (angleSinus >= 0.f) ? -angleSinus * angleTangent * static_cast<float>(widthIn - 1u) : angleTangent * (static_cast<float>(widthIn - 1u) * -angleSinus + (1.f - static_cast<float>(heightOut)));
2677 for(uint32_t y = 0u; y < heightOut; ++y, offset += angleTangent)
2679 const int32_t shear = static_cast<int32_t>(floor(offset));
2680 HorizontalSkew(tmpPixelsInPtr.get(), tmpWidthIn, tmpWidthIn, pixelSize, pixelsOut, widthOut, y, shear, offset - static_cast<float>(shear));
2683 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2684 // @note Allocated memory by the last 'Horizontal Skew' has to be freed by the caller to this function.
2687 void HorizontalShear(const uint8_t* const pixelsIn,
2693 uint8_t*& pixelsOut,
2695 uint32_t& heightOut)
2697 // Calculate the destination image dimensions.
2699 const float absRadians = fabs(radians);
2701 if(absRadians > Math::PI_4)
2703 // Can't shear more than 45 degrees.
2707 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Can't shear more than 45 degrees (PI/4 radians). radians : %f\n", radians);
2711 widthOut = widthIn + static_cast<uint32_t>(ceil(absRadians * static_cast<float>(heightIn)));
2712 heightOut = heightIn;
2714 // Allocate the buffer for the shear.
2715 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2717 if(nullptr == pixelsOut)
2719 DALI_LOG_ERROR("malloc is failed. request malloc size : %u x %u x %u\n", widthOut, heightOut, pixelSize);
2723 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2727 for(uint32_t y = 0u; y < heightOut; ++y)
2729 const float shear = radians * ((radians >= 0.f) ? (0.5f + static_cast<float>(y)) : (0.5f + static_cast<float>(y) - static_cast<float>(heightOut)));
2731 const int32_t intShear = static_cast<int32_t>(floor(shear));
2732 HorizontalSkew(pixelsIn, widthIn, strideIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>(intShear));
2736 } /* namespace Platform */
2737 } /* namespace Internal */
2738 } /* namespace Dali */