2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali/internal/imaging/common/image-operations.h>
21 #include <dali/devel-api/adaptor-framework/image-loading.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/common/dali-vector.h>
24 #include <dali/public-api/math/vector2.h>
26 #include <third-party/resampler/resampler.h>
42 // The BORDER_FILL_VALUE is a single byte value that is used for horizontal and vertical borders.
43 // A value of 0x00 gives us transparency for pixel buffers with an alpha channel, or black otherwise.
44 // We can optionally use a Vector4 color here, but at reduced fill speed.
45 const uint8_t BORDER_FILL_VALUE(0x00);
46 // A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
47 const unsigned int MAXIMUM_TARGET_BITMAP_SIZE((1u << 16) - 1);
49 // Constants used by the ImageResampler.
50 const float DEFAULT_SOURCE_GAMMA = 1.75f; ///< Default source gamma value used in the Resampler() function. Partial gamma correction looks better on mips. Set to 1.0 to disable gamma correction.
51 const float FILTER_SCALE = 1.f; ///< Default filter scale value used in the Resampler() function. Filter scale - values < 1.0 cause aliasing, but create sharper looking mips.
53 const float RAD_135 = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians;
54 const float RAD_225 = RAD_135 + Math::PI_2; ///< 225 degrees in radians;
55 const float RAD_270 = 3.f * Math::PI_2; ///< 270 degrees in radians;
56 const float RAD_315 = RAD_225 + Math::PI_2; ///< 315 degrees in radians;
58 using Integration::Bitmap;
59 using Integration::BitmapPtr;
60 typedef unsigned char PixelBuffer;
63 * @brief 4 byte pixel structure.
71 } __attribute__((packed, aligned(4))); //< Tell the compiler it is okay to use a single 32 bit load.
74 * @brief RGB888 pixel structure.
81 } __attribute__((packed, aligned(1)));
84 * @brief RGB565 pixel typedefed from a short.
86 * Access fields by manual shifting and masking.
88 typedef uint16_t PixelRGB565;
91 * @brief a Pixel composed of two independent byte components.
97 } __attribute__((packed, aligned(2))); //< Tell the compiler it is okay to use a single 16 bit load.
99 #if defined(DEBUG_ENABLED)
101 * Disable logging of image operations or make it verbose from the commandline
102 * as follows (e.g., for dali demo app):
104 * LOG_IMAGE_OPERATIONS=0 dali-demo #< off
105 * LOG_IMAGE_OPERATIONS=3 dali-demo #< on, verbose
108 Debug::Filter* gImageOpsLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_IMAGE_OPERATIONS");
111 /** @return The greatest even number less than or equal to the argument. */
112 inline unsigned int EvenDown(const unsigned int a)
114 const unsigned int evened = a & ~1u;
119 * @brief Log bad parameters.
121 void ValidateScalingParameters(const unsigned int inputWidth,
122 const unsigned int inputHeight,
123 const unsigned int desiredWidth,
124 const unsigned int desiredHeight)
126 if(desiredWidth > inputWidth || desiredHeight > inputHeight)
128 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Upscaling not supported (%u, %u -> %u, %u).\n", inputWidth, inputHeight, desiredWidth, desiredHeight);
131 if(desiredWidth == 0u || desiredHeight == 0u)
133 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Downscaling to a zero-area target is pointless.\n");
136 if(inputWidth == 0u || inputHeight == 0u)
138 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Zero area images cannot be scaled\n");
143 * @brief Do debug assertions common to all scanline halving functions.
144 * @note Inline and in anon namespace so should boil away in release builds.
146 inline void DebugAssertScanlineParameters(const uint8_t* const pixels, const unsigned int width)
148 DALI_ASSERT_DEBUG(pixels && "Null pointer.");
149 DALI_ASSERT_DEBUG(width > 1u && "Can't average fewer than two pixels.");
150 DALI_ASSERT_DEBUG(width < 131072u && "Unusually wide image: are you sure you meant to pass that value in?");
154 * @brief Assertions on params to functions averaging pairs of scanlines.
155 * @note Inline as intended to boil away in release.
157 inline void DebugAssertDualScanlineParameters(const uint8_t* const scanline1,
158 const uint8_t* const scanline2,
159 uint8_t* const outputScanline,
160 const size_t widthInComponents)
162 DALI_ASSERT_DEBUG(scanline1 && "Null pointer.");
163 DALI_ASSERT_DEBUG(scanline2 && "Null pointer.");
164 DALI_ASSERT_DEBUG(outputScanline && "Null pointer.");
165 DALI_ASSERT_DEBUG(((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents)) && "Scanlines alias.");
166 DALI_ASSERT_DEBUG(((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output.");
170 * @brief Converts a scaling mode to the definition of which dimensions matter when box filtering as a part of that mode.
172 BoxDimensionTest DimensionTestForScalingMode(FittingMode::Type fittingMode)
174 BoxDimensionTest dimensionTest;
175 dimensionTest = BoxDimensionTestEither;
179 // Shrink to fit attempts to make one or zero dimensions smaller than the
180 // desired dimensions and one or two dimensions exactly the same as the desired
181 // ones, so as long as one dimension is larger than the desired size, box
182 // filtering can continue even if the second dimension is smaller than the
183 // desired dimensions:
184 case FittingMode::SHRINK_TO_FIT:
186 dimensionTest = BoxDimensionTestEither;
189 // Scale to fill mode keeps both dimensions at least as large as desired:
190 case FittingMode::SCALE_TO_FILL:
192 dimensionTest = BoxDimensionTestBoth;
195 // Y dimension is irrelevant when downscaling in FIT_WIDTH mode:
196 case FittingMode::FIT_WIDTH:
198 dimensionTest = BoxDimensionTestX;
201 // X Dimension is ignored by definition in FIT_HEIGHT mode:
202 case FittingMode::FIT_HEIGHT:
204 dimensionTest = BoxDimensionTestY;
209 return dimensionTest;
213 * @brief Work out the dimensions for a uniform scaling of the input to map it
214 * into the target while effecting ShinkToFit scaling mode.
216 ImageDimensions FitForShrinkToFit(ImageDimensions target, ImageDimensions source)
218 // Scale the input by the least extreme of the two dimensions:
219 const float widthScale = target.GetX() / float(source.GetX());
220 const float heightScale = target.GetY() / float(source.GetY());
221 const float scale = widthScale < heightScale ? widthScale : heightScale;
223 // Do no scaling at all if the result would increase area:
229 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
233 * @brief Work out the dimensions for a uniform scaling of the input to map it
234 * into the target while effecting SCALE_TO_FILL scaling mode.
235 * @note An image scaled into the output dimensions will need either top and
236 * bottom or left and right to be cropped away unless the source was pre-cropped
237 * to match the destination aspect ratio.
239 ImageDimensions FitForScaleToFill(ImageDimensions target, ImageDimensions source)
241 DALI_ASSERT_DEBUG(source.GetX() > 0 && source.GetY() > 0 && "Zero-area rectangles should not be passed-in");
242 // Scale the input by the least extreme of the two dimensions:
243 const float widthScale = target.GetX() / float(source.GetX());
244 const float heightScale = target.GetY() / float(source.GetY());
245 const float scale = widthScale > heightScale ? widthScale : heightScale;
247 // Do no scaling at all if the result would increase area:
253 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
257 * @brief Work out the dimensions for a uniform scaling of the input to map it
258 * into the target while effecting FIT_WIDTH scaling mode.
260 ImageDimensions FitForFitWidth(ImageDimensions target, ImageDimensions source)
262 DALI_ASSERT_DEBUG(source.GetX() > 0 && "Cant fit a zero-dimension rectangle.");
263 const float scale = target.GetX() / float(source.GetX());
265 // Do no scaling at all if the result would increase area:
270 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
274 * @brief Work out the dimensions for a uniform scaling of the input to map it
275 * into the target while effecting FIT_HEIGHT scaling mode.
277 ImageDimensions FitForFitHeight(ImageDimensions target, ImageDimensions source)
279 DALI_ASSERT_DEBUG(source.GetY() > 0 && "Cant fit a zero-dimension rectangle.");
280 const float scale = target.GetY() / float(source.GetY());
282 // Do no scaling at all if the result would increase area:
288 return ImageDimensions(source.GetX() * scale + 0.5f, source.GetY() * scale + 0.5f);
292 * @brief Generate the rectangle to use as the target of a pixel sampling pass
293 * (e.g., nearest or linear).
295 ImageDimensions FitToScalingMode(ImageDimensions requestedSize, ImageDimensions sourceSize, FittingMode::Type fittingMode)
297 ImageDimensions fitDimensions;
300 case FittingMode::SHRINK_TO_FIT:
302 fitDimensions = FitForShrinkToFit(requestedSize, sourceSize);
305 case FittingMode::SCALE_TO_FILL:
307 fitDimensions = FitForScaleToFill(requestedSize, sourceSize);
310 case FittingMode::FIT_WIDTH:
312 fitDimensions = FitForFitWidth(requestedSize, sourceSize);
315 case FittingMode::FIT_HEIGHT:
317 fitDimensions = FitForFitHeight(requestedSize, sourceSize);
322 return fitDimensions;
326 * @brief Calculate the number of lines on the X and Y axis that need to be
327 * either added or removed with repect to the specified fitting mode.
328 * (e.g., nearest or linear).
329 * @param[in] sourceSize The size of the source image
330 * @param[in] fittingMode The fitting mode to use
331 * @param[in/out] requestedSize The target size that the image will be fitted to.
332 * If the source image is smaller than the requested size, the source is not scaled up.
333 * So we reduce the target size while keeping aspect by lowering resolution.
334 * @param[out] scanlinesToCrop The number of scanlines to remove from the image (can be negative to represent Y borders required)
335 * @param[out] columnsToCrop The number of columns to remove from the image (can be negative to represent X borders required)
337 void CalculateBordersFromFittingMode(ImageDimensions sourceSize, FittingMode::Type fittingMode, ImageDimensions& requestedSize, int& scanlinesToCrop, int& columnsToCrop)
339 const int sourceWidth(static_cast<int>(sourceSize.GetWidth()));
340 const int sourceHeight(static_cast<int>(sourceSize.GetHeight()));
341 const float targetAspect(static_cast<float>(requestedSize.GetWidth()) / static_cast<float>(requestedSize.GetHeight()));
347 case FittingMode::FIT_WIDTH:
349 finalWidth = sourceWidth;
350 finalHeight = static_cast<float>(sourceWidth) / targetAspect;
353 scanlinesToCrop = -(finalHeight - sourceHeight);
357 case FittingMode::FIT_HEIGHT:
359 finalWidth = static_cast<float>(sourceHeight) * targetAspect;
360 finalHeight = sourceHeight;
362 columnsToCrop = -(finalWidth - sourceWidth);
367 case FittingMode::SHRINK_TO_FIT:
369 const float sourceAspect(static_cast<float>(sourceWidth) / static_cast<float>(sourceHeight));
370 if(sourceAspect > targetAspect)
372 finalWidth = sourceWidth;
373 finalHeight = static_cast<float>(sourceWidth) / targetAspect;
376 scanlinesToCrop = -(finalHeight - sourceHeight);
380 finalWidth = static_cast<float>(sourceHeight) * targetAspect;
381 finalHeight = sourceHeight;
383 columnsToCrop = -(finalWidth - sourceWidth);
389 case FittingMode::SCALE_TO_FILL:
391 const float sourceAspect(static_cast<float>(sourceWidth) / static_cast<float>(sourceHeight));
392 if(sourceAspect > targetAspect)
394 finalWidth = static_cast<float>(sourceHeight) * targetAspect;
395 finalHeight = sourceHeight;
397 columnsToCrop = -(finalWidth - sourceWidth);
402 finalWidth = sourceWidth;
403 finalHeight = static_cast<float>(sourceWidth) / targetAspect;
406 scanlinesToCrop = -(finalHeight - sourceHeight);
412 requestedSize.SetWidth(finalWidth);
413 requestedSize.SetHeight(finalHeight);
417 * @brief Construct a pixel buffer object from a copy of the pixel array passed in.
419 Dali::Devel::PixelBuffer MakePixelBuffer(const uint8_t* const pixels, Pixel::Format pixelFormat, unsigned int width, unsigned int height)
421 DALI_ASSERT_DEBUG(pixels && "Null bitmap buffer to copy.");
423 // Allocate a pixel buffer to hold the image passed in:
424 auto newBitmap = Dali::Devel::PixelBuffer::New(width, height, pixelFormat);
426 // Copy over the pixels from the downscaled image that was generated in-place in the pixel buffer of the input bitmap:
427 memcpy(newBitmap.GetBuffer(), pixels, width * height * Pixel::GetBytesPerPixel(pixelFormat));
432 * @brief Work out the desired width and height, accounting for zeros.
434 * @param[in] bitmapWidth Width of image before processing.
435 * @param[in] bitmapHeight Height of image before processing.
436 * @param[in] requestedWidth Width of area to scale image into. Can be zero.
437 * @param[in] requestedHeight Height of area to scale image into. Can be zero.
438 * @return Dimensions of area to scale image into after special rules are applied.
440 ImageDimensions CalculateDesiredDimensions(unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight)
442 unsigned int maxSize = Dali::GetMaxTextureSize();
444 // If no dimensions have been requested, default to the source ones:
445 if(requestedWidth == 0 && requestedHeight == 0)
447 if(bitmapWidth <= maxSize && bitmapHeight <= maxSize)
449 return ImageDimensions(bitmapWidth, bitmapHeight);
453 // Calculate the size from the max texture size and the source image aspect ratio
454 if(bitmapWidth > bitmapHeight)
456 return ImageDimensions(maxSize, bitmapHeight * maxSize / static_cast<float>(bitmapWidth) + 0.5f);
460 return ImageDimensions(bitmapWidth * maxSize / static_cast<float>(bitmapHeight) + 0.5f, maxSize);
465 // If both dimensions have values requested, use them both:
466 if(requestedWidth != 0 && requestedHeight != 0)
468 if(requestedWidth <= maxSize && requestedHeight <= maxSize)
470 return ImageDimensions(requestedWidth, requestedHeight);
474 // Calculate the size from the max texture size and the source image aspect ratio
475 if(requestedWidth > requestedHeight)
477 return ImageDimensions(maxSize, requestedHeight * maxSize / static_cast<float>(requestedWidth) + 0.5f);
481 return ImageDimensions(requestedWidth * maxSize / static_cast<float>(requestedHeight) + 0.5f, maxSize);
486 // Only one of the dimensions has been requested. Calculate the other from
487 // the requested one and the source image aspect ratio:
488 if(requestedWidth != 0)
490 requestedWidth = std::min(requestedWidth, maxSize);
491 return ImageDimensions(requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f);
494 requestedHeight = std::min(requestedHeight, maxSize);
495 return ImageDimensions(bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight);
499 * @brief Rotates the given buffer @p pixelsIn 90 degrees counter clockwise.
501 * @note It allocates memory for the returned @p pixelsOut buffer.
502 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
503 * @note It may fail if malloc() fails to allocate memory.
505 * @param[in] pixelsIn The input buffer.
506 * @param[in] widthIn The width of the input buffer.
507 * @param[in] heightIn The height of the input buffer.
508 * @param[in] pixelSize The size of the pixel.
509 * @param[out] pixelsOut The rotated output buffer.
510 * @param[out] widthOut The width of the output buffer.
511 * @param[out] heightOut The height of the output buffer.
513 * @return Whether the rotation succeeded.
515 bool Rotate90(const uint8_t* const pixelsIn,
516 unsigned int widthIn,
517 unsigned int heightIn,
518 unsigned int pixelSize,
520 unsigned int& widthOut,
521 unsigned int& heightOut)
523 // The new size of the image.
527 // Allocate memory for the rotated buffer.
528 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
529 if(nullptr == pixelsOut)
534 // Return if the memory allocations fails.
538 // Rotate the buffer.
539 for(unsigned int y = 0u; y < heightIn; ++y)
541 const unsigned int srcLineIndex = y * widthIn;
542 const unsigned int dstX = y;
543 for(unsigned int x = 0u; x < widthIn; ++x)
545 const unsigned int dstY = heightOut - x - 1u;
546 const unsigned int dstIndex = pixelSize * (dstY * widthOut + dstX);
547 const unsigned int srcIndex = pixelSize * (srcLineIndex + x);
549 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
551 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
560 * @brief Rotates the given buffer @p pixelsIn 180 degrees counter clockwise.
562 * @note It allocates memory for the returned @p pixelsOut buffer.
563 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
564 * @note It may fail if malloc() fails to allocate memory.
566 * @param[in] pixelsIn The input buffer.
567 * @param[in] widthIn The width of the input buffer.
568 * @param[in] heightIn The height of the input buffer.
569 * @param[in] pixelSize The size of the pixel.
570 * @param[out] pixelsOut The rotated output buffer.
572 * @return Whether the rotation succeeded.
574 bool Rotate180(const uint8_t* const pixelsIn,
575 unsigned int widthIn,
576 unsigned int heightIn,
577 unsigned int pixelSize,
580 // Allocate memory for the rotated buffer.
581 pixelsOut = static_cast<uint8_t*>(malloc(widthIn * heightIn * pixelSize));
582 if(nullptr == pixelsOut)
584 // Return if the memory allocations fails.
588 // Rotate the buffer.
589 for(unsigned int y = 0u; y < heightIn; ++y)
591 const unsigned int srcLineIndex = y * widthIn;
592 const unsigned int dstY = heightIn - y - 1u;
593 for(unsigned int x = 0u; x < widthIn; ++x)
595 const unsigned int dstX = widthIn - x - 1u;
596 const unsigned int dstIndex = pixelSize * (dstY * widthIn + dstX);
597 const unsigned int srcIndex = pixelSize * (srcLineIndex + x);
599 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
601 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
610 * @brief Rotates the given buffer @p pixelsIn 270 degrees counter clockwise.
612 * @note It allocates memory for the returned @p pixelsOut buffer.
613 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
614 * @note It may fail if malloc() fails to allocate memory.
616 * @param[in] pixelsIn The input buffer.
617 * @param[in] widthIn The width of the input buffer.
618 * @param[in] heightIn The height of the input buffer.
619 * @param[in] pixelSize The size of the pixel.
620 * @param[out] pixelsOut The rotated output buffer.
621 * @param[out] widthOut The width of the output buffer.
622 * @param[out] heightOut The height of the output buffer.
624 * @return Whether the rotation succeeded.
626 bool Rotate270(const uint8_t* const pixelsIn,
627 unsigned int widthIn,
628 unsigned int heightIn,
629 unsigned int pixelSize,
631 unsigned int& widthOut,
632 unsigned int& heightOut)
634 // The new size of the image.
638 // Allocate memory for the rotated buffer.
639 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
640 if(nullptr == pixelsOut)
645 // Return if the memory allocations fails.
649 // Rotate the buffer.
650 for(unsigned int y = 0u; y < heightIn; ++y)
652 const unsigned int srcLineIndex = y * widthIn;
653 const unsigned int dstX = widthOut - y - 1u;
654 for(unsigned int x = 0u; x < widthIn; ++x)
656 const unsigned int dstY = x;
657 const unsigned int dstIndex = pixelSize * (dstY * widthOut + dstX);
658 const unsigned int srcIndex = pixelSize * (srcLineIndex + x);
660 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
662 *(pixelsOut + dstIndex + channel) = *(pixelsIn + srcIndex + channel);
671 * @brief Skews a row horizontally (with filtered weights)
673 * @note Limited to 45 degree skewing only.
674 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
676 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
677 * @param[in] srcWidth The width of the input pixel buffer.
678 * @param[in] pixelSize The size of the pixel.
679 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
680 * @param[in] dstWidth The width of the output pixel buffer.
681 * @param[in] row The row index.
682 * @param[in] offset The skew offset.
683 * @param[in] weight The relative weight of right pixel.
685 void HorizontalSkew(const uint8_t* const srcBufferPtr,
687 unsigned int pixelSize,
688 uint8_t*& dstBufferPtr,
696 // Fill gap left of skew with background.
697 memset(dstBufferPtr + row * pixelSize * dstWidth, 0u, pixelSize * offset);
700 unsigned char oldLeft[4u] = {0u, 0u, 0u, 0u};
703 for(i = 0u; i < srcWidth; ++i)
705 // Loop through row pixels
706 const unsigned int srcIndex = pixelSize * (row * srcWidth + i);
708 unsigned char src[4u] = {0u, 0u, 0u, 0u};
709 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
711 src[channel] = *(srcBufferPtr + srcIndex + channel);
715 unsigned char left[4u] = {0u, 0u, 0u, 0u};
716 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
718 left[channel] = static_cast<unsigned char>(static_cast<float>(src[channel]) * weight);
720 // Update left over on source
721 src[channel] -= (left[channel] - oldLeft[channel]);
725 if((i + offset >= 0) && (i + offset < dstWidth))
727 const unsigned int dstIndex = pixelSize * (row * dstWidth + i + offset);
729 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
731 *(dstBufferPtr + dstIndex + channel) = src[channel];
735 // Save leftover for next pixel in scan
736 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
738 oldLeft[channel] = left[channel];
742 // Go to rightmost point of skew
746 // If still in image bounds, put leftovers there
747 const unsigned int dstIndex = pixelSize * (row * dstWidth + i);
749 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
751 *(dstBufferPtr + dstIndex + channel) = oldLeft[channel];
754 // Clear to the right of the skewed line with background
756 memset(dstBufferPtr + pixelSize * (row * dstWidth + i), 0u, pixelSize * (dstWidth - i));
761 * @brief Skews a column vertically (with filtered weights)
763 * @note Limited to 45 degree skewing only.
764 * @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
766 * @param[in] srcBufferPtr Pointer to the input pixel buffer.
767 * @param[in] srcWidth The width of the input pixel buffer.
768 * @param[in] srcHeight The height of the input pixel buffer.
769 * @param[in] pixelSize The size of the pixel.
770 * @param[in,out] dstPixelBuffer Pointer to the output pixel buffer.
771 * @param[in] dstWidth The width of the output pixel buffer.
772 * @param[in] dstHeight The height of the output pixel buffer.
773 * @param[in] column The column index.
774 * @param[in] offset The skew offset.
775 * @param[in] weight The relative weight of uppeer pixel.
777 void VerticalSkew(const uint8_t* const srcBufferPtr,
780 unsigned int pixelSize,
781 uint8_t*& dstBufferPtr,
788 for(int i = 0; i < offset; ++i)
790 // Fill gap above skew with background
791 const unsigned int dstIndex = pixelSize * (i * dstWidth + column);
793 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
795 *(dstBufferPtr + dstIndex + channel) = 0u;
799 unsigned char oldLeft[4u] = {0u, 0u, 0u, 0u};
803 for(i = 0; i < srcHeight; ++i)
805 // Loop through column pixels
806 const unsigned int srcIndex = pixelSize * (i * srcWidth + column);
808 unsigned char src[4u] = {0u, 0u, 0u, 0u};
809 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
811 src[channel] = *(srcBufferPtr + srcIndex + channel);
817 unsigned char left[4u] = {0u, 0u, 0u, 0u};
818 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
820 left[channel] = static_cast<unsigned char>(static_cast<float>(src[channel]) * weight);
821 // Update left over on source
822 src[channel] -= (left[channel] - oldLeft[channel]);
826 if((yPos >= 0) && (yPos < dstHeight))
828 const unsigned int dstIndex = pixelSize * (yPos * dstWidth + column);
830 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
832 *(dstBufferPtr + dstIndex + channel) = src[channel];
836 // Save leftover for next pixel in scan
837 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
839 oldLeft[channel] = left[channel];
843 // Go to bottom point of skew
847 // If still in image bounds, put leftovers there
848 const unsigned int dstIndex = pixelSize * (i * dstWidth + column);
850 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
852 *(dstBufferPtr + dstIndex + channel) = oldLeft[channel];
856 while(++i < dstHeight)
858 // Clear below skewed line with background
859 const unsigned int dstIndex = pixelSize * (i * dstWidth + column);
861 for(unsigned int channel = 0u; channel < pixelSize; ++channel)
863 *(dstBufferPtr + dstIndex + channel) = 0u;
870 ImageDimensions CalculateDesiredDimensions(ImageDimensions rawDimensions, ImageDimensions requestedDimensions)
872 return CalculateDesiredDimensions(rawDimensions.GetWidth(), rawDimensions.GetHeight(), requestedDimensions.GetWidth(), requestedDimensions.GetHeight());
876 * @brief Apply cropping and padding for specified fitting mode.
878 * Once the bitmap has been (optionally) downscaled to an appropriate size, this method performs alterations
879 * based on the fitting mode.
881 * This will add vertical or horizontal borders if necessary.
882 * Crop the source image data vertically or horizontally if necessary.
883 * The aspect of the source image is preserved.
884 * If the source image is smaller than the desired size, the algorithm will modify the the newly created
885 * bitmaps dimensions to only be as large as necessary, as a memory saving optimization. This will cause
886 * GPU scaling to be performed at render time giving the same result with less texture traversal.
888 * @param[in] bitmap The source pixel buffer to perform modifications on.
889 * @param[in] desiredDimensions The target dimensions to aim to fill based on the fitting mode.
890 * @param[in] fittingMode The fitting mode to use.
892 * @return A new bitmap with the padding and cropping required for fitting mode applied.
893 * If no modification is needed or possible, the passed in bitmap is returned.
895 Dali::Devel::PixelBuffer CropAndPadForFittingMode(Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode);
898 * @brief Adds horizontal or vertical borders to the source image.
900 * @param[in] targetPixels The destination image pointer to draw the borders on.
901 * @param[in] bytesPerPixel The number of bytes per pixel of the target pixel buffer.
902 * @param[in] targetDimensions The dimensions of the destination image.
903 * @param[in] padDimensions The columns and scanlines to pad with borders.
905 void AddBorders(PixelBuffer* targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions);
907 Dali::Devel::PixelBuffer ApplyAttributesToBitmap(Dali::Devel::PixelBuffer bitmap, ImageDimensions dimensions, FittingMode::Type fittingMode, SamplingMode::Type samplingMode)
911 // Calculate the desired box, accounting for a possible zero component:
912 const ImageDimensions desiredDimensions = CalculateDesiredDimensions(bitmap.GetWidth(), bitmap.GetHeight(), dimensions.GetWidth(), dimensions.GetHeight());
914 // If a different size than the raw one has been requested, resize the image
915 // maximally using a repeated box filter without making it smaller than the
916 // requested size in either dimension:
917 bitmap = DownscaleBitmap(bitmap, desiredDimensions, fittingMode, samplingMode);
919 // Cut the bitmap according to the desired width and height so that the
920 // resulting bitmap has the same aspect ratio as the desired dimensions.
921 // Add crop and add borders if necessary depending on fitting mode.
924 bitmap = CropAndPadForFittingMode(bitmap, desiredDimensions, fittingMode);
931 Dali::Devel::PixelBuffer CropAndPadForFittingMode(Dali::Devel::PixelBuffer& bitmap, ImageDimensions desiredDimensions, FittingMode::Type fittingMode)
933 const unsigned int inputWidth = bitmap.GetWidth();
934 const unsigned int inputHeight = bitmap.GetHeight();
936 if(desiredDimensions.GetWidth() < 1u || desiredDimensions.GetHeight() < 1u)
938 DALI_LOG_WARNING("Image scaling aborted as desired dimensions too small (%u, %u).\n", desiredDimensions.GetWidth(), desiredDimensions.GetHeight());
940 else if(inputWidth != desiredDimensions.GetWidth() || inputHeight != desiredDimensions.GetHeight())
942 // Calculate any padding or cropping that needs to be done based on the fitting mode.
943 // Note: If the desired size is larger than the original image, the desired size will be
944 // reduced while maintaining the aspect, in order to save unnecessary memory usage.
945 int scanlinesToCrop = 0;
946 int columnsToCrop = 0;
948 CalculateBordersFromFittingMode(ImageDimensions(inputWidth, inputHeight), fittingMode, desiredDimensions, scanlinesToCrop, columnsToCrop);
950 unsigned int desiredWidth(desiredDimensions.GetWidth());
951 unsigned int desiredHeight(desiredDimensions.GetHeight());
953 // Action the changes by making a new bitmap with the central part of the loaded one if required.
954 if(scanlinesToCrop != 0 || columnsToCrop != 0)
956 // Split the adding and removing of scanlines and columns into separate variables,
957 // so we can use one piece of generic code to action the changes.
958 unsigned int scanlinesToPad = 0;
959 unsigned int columnsToPad = 0;
960 if(scanlinesToCrop < 0)
962 scanlinesToPad = -scanlinesToCrop;
965 if(columnsToCrop < 0)
967 columnsToPad = -columnsToCrop;
971 // If there is no filtering, then the final image size can become very large, exit if larger than maximum.
972 if((desiredWidth > MAXIMUM_TARGET_BITMAP_SIZE) || (desiredHeight > MAXIMUM_TARGET_BITMAP_SIZE) ||
973 (columnsToPad > MAXIMUM_TARGET_BITMAP_SIZE) || (scanlinesToPad > MAXIMUM_TARGET_BITMAP_SIZE))
975 DALI_LOG_WARNING("Image scaling aborted as final dimensions too large (%u, %u).\n", desiredWidth, desiredHeight);
979 // Create new PixelBuffer with the desired size.
980 const auto pixelFormat = bitmap.GetPixelFormat();
982 auto croppedBitmap = Devel::PixelBuffer::New(desiredWidth, desiredHeight, pixelFormat);
984 // Add some pre-calculated offsets to the bitmap pointers so this is not done within a loop.
985 // The cropping is added to the source pointer, and the padding is added to the destination.
986 const auto bytesPerPixel = Pixel::GetBytesPerPixel(pixelFormat);
987 const PixelBuffer* const sourcePixels = bitmap.GetBuffer() + ((((scanlinesToCrop / 2) * inputWidth) + (columnsToCrop / 2)) * bytesPerPixel);
988 PixelBuffer* const targetPixels = croppedBitmap.GetBuffer();
989 PixelBuffer* const targetPixelsActive = targetPixels + ((((scanlinesToPad / 2) * desiredWidth) + (columnsToPad / 2)) * bytesPerPixel);
990 DALI_ASSERT_DEBUG(sourcePixels && targetPixels);
992 // Copy the image data to the new bitmap.
993 // Optimize to a single memcpy if the left and right edges don't need a crop or a pad.
994 unsigned int outputSpan(desiredWidth * bytesPerPixel);
995 if(columnsToCrop == 0 && columnsToPad == 0)
997 memcpy(targetPixelsActive, sourcePixels, (desiredHeight - scanlinesToPad) * outputSpan);
1001 // The width needs to change (due to either a crop or a pad), so we copy a scanline at a time.
1002 // Precalculate any constants to optimize the inner loop.
1003 const unsigned int inputSpan(inputWidth * bytesPerPixel);
1004 const unsigned int copySpan((desiredWidth - columnsToPad) * bytesPerPixel);
1005 const unsigned int scanlinesToCopy(desiredHeight - scanlinesToPad);
1007 for(unsigned int y = 0; y < scanlinesToCopy; ++y)
1009 memcpy(&targetPixelsActive[y * outputSpan], &sourcePixels[y * inputSpan], copySpan);
1013 // Add vertical or horizontal borders to the final image (if required).
1014 desiredDimensions.SetWidth(desiredWidth);
1015 desiredDimensions.SetHeight(desiredHeight);
1016 AddBorders(croppedBitmap.GetBuffer(), bytesPerPixel, desiredDimensions, ImageDimensions(columnsToPad, scanlinesToPad));
1017 // Overwrite the loaded bitmap with the cropped version
1018 bitmap = croppedBitmap;
1025 void AddBorders(PixelBuffer* targetPixels, const unsigned int bytesPerPixel, const ImageDimensions targetDimensions, const ImageDimensions padDimensions)
1027 // Assign ints for faster access.
1028 unsigned int desiredWidth(targetDimensions.GetWidth());
1029 unsigned int desiredHeight(targetDimensions.GetHeight());
1030 unsigned int columnsToPad(padDimensions.GetWidth());
1031 unsigned int scanlinesToPad(padDimensions.GetHeight());
1032 unsigned int outputSpan(desiredWidth * bytesPerPixel);
1034 // Add letterboxing (symmetrical borders) if needed.
1035 if(scanlinesToPad > 0)
1037 // Add a top border. Note: This is (deliberately) rounded down if padding is an odd number.
1038 memset(targetPixels, BORDER_FILL_VALUE, (scanlinesToPad / 2) * outputSpan);
1040 // We subtract scanlinesToPad/2 from scanlinesToPad so that we have the correct
1041 // offset for odd numbers (as the top border is 1 pixel smaller in these cases.
1042 unsigned int bottomBorderHeight = scanlinesToPad - (scanlinesToPad / 2);
1045 memset(&targetPixels[(desiredHeight - bottomBorderHeight) * outputSpan], BORDER_FILL_VALUE, bottomBorderHeight * outputSpan);
1047 else if(columnsToPad > 0)
1049 // Add a left and right border.
1051 // Pre-calculate span size outside of loop.
1052 unsigned int leftBorderSpanWidth((columnsToPad / 2) * bytesPerPixel);
1053 for(unsigned int y = 0; y < desiredHeight; ++y)
1055 memset(&targetPixels[y * outputSpan], BORDER_FILL_VALUE, leftBorderSpanWidth);
1059 // Pre-calculate the initial x offset as it is always the same for a small optimization.
1060 // We subtract columnsToPad/2 from columnsToPad so that we have the correct
1061 // offset for odd numbers (as the left border is 1 pixel smaller in these cases.
1062 unsigned int rightBorderWidth = columnsToPad - (columnsToPad / 2);
1063 PixelBuffer* const destPixelsRightBorder(targetPixels + ((desiredWidth - rightBorderWidth) * bytesPerPixel));
1064 unsigned int rightBorderSpanWidth = rightBorderWidth * bytesPerPixel;
1066 for(unsigned int y = 0; y < desiredHeight; ++y)
1068 memset(&destPixelsRightBorder[y * outputSpan], BORDER_FILL_VALUE, rightBorderSpanWidth);
1073 Dali::Devel::PixelBuffer DownscaleBitmap(Dali::Devel::PixelBuffer bitmap,
1074 ImageDimensions desired,
1075 FittingMode::Type fittingMode,
1076 SamplingMode::Type samplingMode)
1078 // Source dimensions as loaded from resources (e.g. filesystem):
1079 auto bitmapWidth = bitmap.GetWidth();
1080 auto bitmapHeight = bitmap.GetHeight();
1081 // Desired dimensions (the rectangle to fit the source image to):
1082 auto desiredWidth = desired.GetWidth();
1083 auto desiredHeight = desired.GetHeight();
1085 Dali::Devel::PixelBuffer outputBitmap{bitmap};
1087 // If a different size than the raw one has been requested, resize the image:
1089 (desiredWidth > 0.0f) && (desiredHeight > 0.0f) &&
1090 ((desiredWidth < bitmapWidth) || (desiredHeight < bitmapHeight)))
1092 auto pixelFormat = bitmap.GetPixelFormat();
1094 // Do the fast power of 2 iterated box filter to get to roughly the right side if the filter mode requests that:
1095 unsigned int shrunkWidth = -1, shrunkHeight = -1;
1096 DownscaleInPlacePow2(bitmap.GetBuffer(), pixelFormat, bitmapWidth, bitmapHeight, desiredWidth, desiredHeight, fittingMode, samplingMode, shrunkWidth, shrunkHeight);
1098 // Work out the dimensions of the downscaled bitmap, given the scaling mode and desired dimensions:
1099 const ImageDimensions filteredDimensions = FitToScalingMode(ImageDimensions(desiredWidth, desiredHeight), ImageDimensions(shrunkWidth, shrunkHeight), fittingMode);
1100 const unsigned int filteredWidth = filteredDimensions.GetWidth();
1101 const unsigned int filteredHeight = filteredDimensions.GetHeight();
1103 // Run a filter to scale down the bitmap if it needs it:
1104 bool filtered = false;
1105 if(filteredWidth < shrunkWidth || filteredHeight < shrunkHeight)
1107 if(samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR ||
1108 samplingMode == SamplingMode::NEAREST || samplingMode == SamplingMode::BOX_THEN_NEAREST)
1110 outputBitmap = Dali::Devel::PixelBuffer::New(filteredWidth, filteredHeight, pixelFormat);
1114 if(samplingMode == SamplingMode::LINEAR || samplingMode == SamplingMode::BOX_THEN_LINEAR)
1116 LinearSample(bitmap.GetBuffer(), ImageDimensions(shrunkWidth, shrunkHeight), pixelFormat, outputBitmap.GetBuffer(), filteredDimensions);
1120 PointSample(bitmap.GetBuffer(), shrunkWidth, shrunkHeight, pixelFormat, outputBitmap.GetBuffer(), filteredWidth, filteredHeight);
1126 // Copy out the 2^x downscaled, box-filtered pixels if no secondary filter (point or linear) was applied:
1127 if(filtered == false && (shrunkWidth < bitmapWidth || shrunkHeight < bitmapHeight))
1129 outputBitmap = MakePixelBuffer(bitmap.GetBuffer(), pixelFormat, shrunkWidth, shrunkHeight);
1133 return outputBitmap;
1139 * @brief Returns whether to keep box filtering based on whether downscaled dimensions will overshoot the desired ones aty the next step.
1140 * @param test Which combination of the two dimensions matter for terminating the filtering.
1141 * @param scaledWidth The width of the current downscaled image.
1142 * @param scaledHeight The height of the current downscaled image.
1143 * @param desiredWidth The target width for the downscaling.
1144 * @param desiredHeight The target height for the downscaling.
1146 bool ContinueScaling(BoxDimensionTest test, unsigned int scaledWidth, unsigned int scaledHeight, unsigned int desiredWidth, unsigned int desiredHeight)
1148 bool keepScaling = false;
1149 const unsigned int nextWidth = scaledWidth >> 1u;
1150 const unsigned int nextHeight = scaledHeight >> 1u;
1152 if(nextWidth >= 1u && nextHeight >= 1u)
1156 case BoxDimensionTestEither:
1158 keepScaling = nextWidth >= desiredWidth || nextHeight >= desiredHeight;
1161 case BoxDimensionTestBoth:
1163 keepScaling = nextWidth >= desiredWidth && nextHeight >= desiredHeight;
1166 case BoxDimensionTestX:
1168 keepScaling = nextWidth >= desiredWidth;
1171 case BoxDimensionTestY:
1173 keepScaling = nextHeight >= desiredHeight;
1183 * @brief A shared implementation of the overall iterative box filter
1184 * downscaling algorithm.
1186 * Specialise this for particular pixel formats by supplying the number of bytes
1187 * per pixel and two functions: one for averaging pairs of neighbouring pixels
1188 * on a single scanline, and a second for averaging pixels at corresponding
1189 * positions on different scanlines.
1192 int BYTES_PER_PIXEL,
1193 void (*HalveScanlineInPlace)(unsigned char* const pixels, const unsigned int width),
1194 void (*AverageScanlines)(const unsigned char* const scanline1, const unsigned char* const __restrict__ scanline2, unsigned char* const outputScanline, const unsigned int width)>
1195 void DownscaleInPlacePow2Generic(unsigned char* const pixels,
1196 const unsigned int inputWidth,
1197 const unsigned int inputHeight,
1198 const unsigned int desiredWidth,
1199 const unsigned int desiredHeight,
1200 BoxDimensionTest dimensionTest,
1202 unsigned& outHeight)
1208 ValidateScalingParameters(inputWidth, inputHeight, desiredWidth, desiredHeight);
1210 // Scale the image until it would be smaller than desired, stopping if the
1211 // resulting height or width would be less than 1:
1212 unsigned int scaledWidth = inputWidth, scaledHeight = inputHeight;
1213 while(ContinueScaling(dimensionTest, scaledWidth, scaledHeight, desiredWidth, desiredHeight))
1215 const unsigned int lastWidth = scaledWidth;
1217 scaledHeight >>= 1u;
1219 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Scaling to %u\t%u.\n", scaledWidth, scaledHeight);
1221 const unsigned int lastScanlinePair = scaledHeight - 1;
1223 // Scale pairs of scanlines until any spare one at the end is dropped:
1224 for(unsigned int y = 0; y <= lastScanlinePair; ++y)
1226 // Scale two scanlines horizontally:
1227 HalveScanlineInPlace(&pixels[y * 2 * lastWidth * BYTES_PER_PIXEL], lastWidth);
1228 HalveScanlineInPlace(&pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL], lastWidth);
1230 // Scale vertical pairs of pixels while the last two scanlines are still warm in
1231 // the CPU cache(s):
1232 // Note, better access patterns for cache-coherence are possible for very large
1233 // images but even a 4k wide RGB888 image will use just 24kB of cache (4k pixels
1234 // * 3 Bpp * 2 scanlines) for two scanlines on the first iteration.
1236 &pixels[y * 2 * lastWidth * BYTES_PER_PIXEL],
1237 &pixels[(y * 2 + 1) * lastWidth * BYTES_PER_PIXEL],
1238 &pixels[y * scaledWidth * BYTES_PER_PIXEL],
1243 ///@note: we could finish off with one of two mutually exclusive passes, one squashing horizontally as far as possible, and the other vertically, if we knew a following cpu point or bilinear filter would restore the desired aspect ratio.
1244 outWidth = scaledWidth;
1245 outHeight = scaledHeight;
1250 void HalveScanlineInPlaceRGB888(unsigned char* const pixels, const unsigned int width)
1252 DebugAssertScanlineParameters(pixels, width);
1254 const unsigned int lastPair = EvenDown(width - 2);
1258 * for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1260 * // Load all the byte pixel components we need:
1261 * const unsigned int c11 = pixels[pixel * 3];
1262 * const unsigned int c12 = pixels[pixel * 3 + 1];
1263 * const unsigned int c13 = pixels[pixel * 3 + 2];
1264 * const unsigned int c21 = pixels[pixel * 3 + 3];
1265 * const unsigned int c22 = pixels[pixel * 3 + 4];
1266 * const unsigned int c23 = pixels[pixel * 3 + 5];
1268 * // Save the averaged byte pixel components:
1269 * pixels[outPixel * 3] = static_cast<unsigned char>(AverageComponent(c11, c21));
1270 * pixels[outPixel * 3 + 1] = static_cast<unsigned char>(AverageComponent(c12, c22));
1271 * pixels[outPixel * 3 + 2] = static_cast<unsigned char>(AverageComponent(c13, c23));
1275 //@ToDo : Fix here if we found that collect 12 bytes == 3 uint32_t with 4 colors, and calculate in one-operation
1276 std::uint8_t* inPixelPtr = pixels;
1277 std::uint8_t* outPixelPtr = pixels;
1278 for(std::uint32_t scanedPixelCount = 0; scanedPixelCount <= lastPair; scanedPixelCount += 2)
1280 *(outPixelPtr + 0) = ((*(inPixelPtr + 0) ^ *(inPixelPtr + 3)) >> 1) + (*(inPixelPtr + 0) & *(inPixelPtr + 3));
1281 *(outPixelPtr + 1) = ((*(inPixelPtr + 1) ^ *(inPixelPtr + 4)) >> 1) + (*(inPixelPtr + 1) & *(inPixelPtr + 4));
1282 *(outPixelPtr + 2) = ((*(inPixelPtr + 2) ^ *(inPixelPtr + 5)) >> 1) + (*(inPixelPtr + 2) & *(inPixelPtr + 5));
1288 void HalveScanlineInPlaceRGBA8888(unsigned char* const pixels, const unsigned int width)
1290 DebugAssertScanlineParameters(pixels, width);
1291 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1293 uint32_t* const alignedPixels = reinterpret_cast<uint32_t*>(pixels);
1295 const unsigned int lastPair = EvenDown(width - 2);
1297 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1299 const uint32_t averaged = AveragePixelRGBA8888(alignedPixels[pixel], alignedPixels[pixel + 1]);
1300 alignedPixels[outPixel] = averaged;
1304 void HalveScanlineInPlaceRGB565(unsigned char* pixels, unsigned int width)
1306 DebugAssertScanlineParameters(pixels, width);
1307 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1309 uint16_t* const alignedPixels = reinterpret_cast<uint16_t*>(pixels);
1311 const unsigned int lastPair = EvenDown(width - 2);
1313 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1315 const uint32_t averaged = AveragePixelRGB565(alignedPixels[pixel], alignedPixels[pixel + 1]);
1316 alignedPixels[outPixel] = averaged;
1320 void HalveScanlineInPlace2Bytes(unsigned char* const pixels, const unsigned int width)
1322 DebugAssertScanlineParameters(pixels, width);
1324 const unsigned int lastPair = EvenDown(width - 2);
1326 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1330 * // Load all the byte pixel components we need:
1331 * const unsigned int c11 = pixels[pixel * 2];
1332 * const unsigned int c12 = pixels[pixel * 2 + 1];
1333 * const unsigned int c21 = pixels[pixel * 2 + 2];
1334 * const unsigned int c22 = pixels[pixel * 2 + 3];
1336 * // Save the averaged byte pixel components:
1337 * pixels[outPixel * 2] = static_cast<unsigned char>(AverageComponent(c11, c21));
1338 * pixels[outPixel * 2 + 1] = static_cast<unsigned char>(AverageComponent(c12, c22));
1341 // Note : We can assume that pixel is even number. So we can use | operation instead of + operation.
1342 pixels[(outPixel << 1)] = ((pixels[(pixel << 1)] ^ pixels[(pixel << 1) | 2]) >> 1) + (pixels[(pixel << 1)] & pixels[(pixel << 1) | 2]);
1343 pixels[(outPixel << 1) | 1] = ((pixels[(pixel << 1) | 1] ^ pixels[(pixel << 1) | 3]) >> 1) + (pixels[(pixel << 1) | 1] & pixels[(pixel << 1) | 3]);
1347 void HalveScanlineInPlace1Byte(unsigned char* const pixels, const unsigned int width)
1349 DebugAssertScanlineParameters(pixels, width);
1351 const unsigned int lastPair = EvenDown(width - 2);
1353 for(unsigned int pixel = 0, outPixel = 0; pixel <= lastPair; pixel += 2, ++outPixel)
1357 * // Load all the byte pixel components we need:
1358 * const unsigned int c1 = pixels[pixel];
1359 * const unsigned int c2 = pixels[pixel + 1];
1361 * // Save the averaged byte pixel component:
1362 * pixels[outPixel] = static_cast<unsigned char>(AverageComponent(c1, c2));
1365 // Note : We can assume that pixel is even number. So we can use | operation instead of + operation.
1366 pixels[outPixel] = ((pixels[pixel] ^ pixels[pixel | 1]) >> 1) + (pixels[pixel] & pixels[pixel | 1]);
1371 * @ToDo: Optimise for ARM using a 4 bytes at a time loop wrapped around the single ARMV6 instruction: UHADD8 R4, R0, R5. Note, this is not neon. It runs in the normal integer pipeline so there is no downside like a stall moving between integer and copro, or extra power for clocking-up the idle copro.
1372 * if (widthInComponents >= 7) { word32* aligned1 = scanline1 + 3 & 3; word32* aligned1_end = scanline1 + widthInPixels & 3; while(aligned1 < aligned1_end) { UHADD8 *aligned1++, *aligned2++, *alignedoutput++ } .. + 0 to 3 spare pixels at each end.
1374 void AverageScanlines1(const unsigned char* const scanline1,
1375 const unsigned char* const __restrict__ scanline2,
1376 unsigned char* const outputScanline,
1377 const unsigned int width)
1379 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width);
1381 for(unsigned int component = 0; component < width; ++component)
1383 outputScanline[component] = static_cast<unsigned char>(AverageComponent(scanline1[component], scanline2[component]));
1387 void AverageScanlines2(const unsigned char* const scanline1,
1388 const unsigned char* const __restrict__ scanline2,
1389 unsigned char* const outputScanline,
1390 const unsigned int width)
1392 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 2);
1394 for(unsigned int component = 0; component < width * 2; ++component)
1396 outputScanline[component] = static_cast<unsigned char>(AverageComponent(scanline1[component], scanline2[component]));
1400 void AverageScanlines3(const unsigned char* const scanline1,
1401 const unsigned char* const __restrict__ scanline2,
1402 unsigned char* const outputScanline,
1403 const unsigned int width)
1405 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 3);
1407 for(unsigned int component = 0; component < width * 3; ++component)
1409 outputScanline[component] = static_cast<unsigned char>(AverageComponent(scanline1[component], scanline2[component]));
1413 void AverageScanlinesRGBA8888(const unsigned char* const scanline1,
1414 const unsigned char* const __restrict__ scanline2,
1415 unsigned char* const outputScanline,
1416 const unsigned int width)
1418 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 4);
1419 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline1) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1420 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline2) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1421 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(outputScanline) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1423 const uint32_t* const alignedScanline1 = reinterpret_cast<const uint32_t*>(scanline1);
1424 const uint32_t* const alignedScanline2 = reinterpret_cast<const uint32_t*>(scanline2);
1425 uint32_t* const alignedOutput = reinterpret_cast<uint32_t*>(outputScanline);
1427 for(unsigned int pixel = 0; pixel < width; ++pixel)
1429 alignedOutput[pixel] = AveragePixelRGBA8888(alignedScanline1[pixel], alignedScanline2[pixel]);
1433 void AverageScanlinesRGB565(const unsigned char* const scanline1,
1434 const unsigned char* const __restrict__ scanline2,
1435 unsigned char* const outputScanline,
1436 const unsigned int width)
1438 DebugAssertDualScanlineParameters(scanline1, scanline2, outputScanline, width * 2);
1439 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline1) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1440 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(scanline2) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1441 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(outputScanline) & 1u) == 0u) && "Pointer should be 2-byte aligned for performance on some platforms.");
1443 const uint16_t* const alignedScanline1 = reinterpret_cast<const uint16_t*>(scanline1);
1444 const uint16_t* const alignedScanline2 = reinterpret_cast<const uint16_t*>(scanline2);
1445 uint16_t* const alignedOutput = reinterpret_cast<uint16_t*>(outputScanline);
1447 for(unsigned int pixel = 0; pixel < width; ++pixel)
1449 alignedOutput[pixel] = AveragePixelRGB565(alignedScanline1[pixel], alignedScanline2[pixel]);
1453 /// Dispatch to pixel format appropriate box filter downscaling functions.
1454 void DownscaleInPlacePow2(unsigned char* const pixels,
1455 Pixel::Format pixelFormat,
1456 unsigned int inputWidth,
1457 unsigned int inputHeight,
1458 unsigned int desiredWidth,
1459 unsigned int desiredHeight,
1460 FittingMode::Type fittingMode,
1461 SamplingMode::Type samplingMode,
1463 unsigned& outHeight)
1465 outWidth = inputWidth;
1466 outHeight = inputHeight;
1467 // Perform power of 2 iterated 4:1 box filtering if the requested filter mode requires it:
1468 if(samplingMode == SamplingMode::BOX || samplingMode == SamplingMode::BOX_THEN_NEAREST || samplingMode == SamplingMode::BOX_THEN_LINEAR)
1470 // Check the pixel format is one that is supported:
1471 if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1473 const BoxDimensionTest dimensionTest = DimensionTestForScalingMode(fittingMode);
1475 if(pixelFormat == Pixel::RGBA8888)
1477 Internal::Platform::DownscaleInPlacePow2RGBA8888(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1479 else if(pixelFormat == Pixel::RGB888)
1481 Internal::Platform::DownscaleInPlacePow2RGB888(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1483 else if(pixelFormat == Pixel::RGB565)
1485 Internal::Platform::DownscaleInPlacePow2RGB565(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1487 else if(pixelFormat == Pixel::LA88)
1489 Internal::Platform::DownscaleInPlacePow2ComponentPair(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1491 else if(pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1493 Internal::Platform::DownscaleInPlacePow2SingleBytePerPixel(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1497 DALI_ASSERT_DEBUG(false && "Inner branch conditions don't match outer branch.");
1503 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not shrunk: unsupported pixel format: %u.\n", unsigned(pixelFormat));
1507 void DownscaleInPlacePow2RGB888(unsigned char* pixels,
1508 unsigned int inputWidth,
1509 unsigned int inputHeight,
1510 unsigned int desiredWidth,
1511 unsigned int desiredHeight,
1512 BoxDimensionTest dimensionTest,
1514 unsigned& outHeight)
1516 DownscaleInPlacePow2Generic<3, HalveScanlineInPlaceRGB888, AverageScanlines3>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1519 void DownscaleInPlacePow2RGBA8888(unsigned char* pixels,
1520 unsigned int inputWidth,
1521 unsigned int inputHeight,
1522 unsigned int desiredWidth,
1523 unsigned int desiredHeight,
1524 BoxDimensionTest dimensionTest,
1526 unsigned& outHeight)
1528 DALI_ASSERT_DEBUG(((reinterpret_cast<ptrdiff_t>(pixels) & 3u) == 0u) && "Pointer should be 4-byte aligned for performance on some platforms.");
1529 DownscaleInPlacePow2Generic<4, HalveScanlineInPlaceRGBA8888, AverageScanlinesRGBA8888>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1532 void DownscaleInPlacePow2RGB565(unsigned char* pixels,
1533 unsigned int inputWidth,
1534 unsigned int inputHeight,
1535 unsigned int desiredWidth,
1536 unsigned int desiredHeight,
1537 BoxDimensionTest dimensionTest,
1538 unsigned int& outWidth,
1539 unsigned int& outHeight)
1541 DownscaleInPlacePow2Generic<2, HalveScanlineInPlaceRGB565, AverageScanlinesRGB565>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1545 * @copydoc DownscaleInPlacePow2RGB888
1547 * For 2-byte formats such as lum8alpha8, but not packed 16 bit formats like RGB565.
1549 void DownscaleInPlacePow2ComponentPair(unsigned char* pixels,
1550 unsigned int inputWidth,
1551 unsigned int inputHeight,
1552 unsigned int desiredWidth,
1553 unsigned int desiredHeight,
1554 BoxDimensionTest dimensionTest,
1556 unsigned& outHeight)
1558 DownscaleInPlacePow2Generic<2, HalveScanlineInPlace2Bytes, AverageScanlines2>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1561 void DownscaleInPlacePow2SingleBytePerPixel(unsigned char* pixels,
1562 unsigned int inputWidth,
1563 unsigned int inputHeight,
1564 unsigned int desiredWidth,
1565 unsigned int desiredHeight,
1566 BoxDimensionTest dimensionTest,
1567 unsigned int& outWidth,
1568 unsigned int& outHeight)
1570 DownscaleInPlacePow2Generic<1, HalveScanlineInPlace1Byte, AverageScanlines1>(pixels, inputWidth, inputHeight, desiredWidth, desiredHeight, dimensionTest, outWidth, outHeight);
1576 * @brief Point sample an image to a new resolution (like GL_NEAREST).
1578 * Template is used purely as a type-safe code generator in this one
1579 * compilation unit. Generated code is inlined into type-specific wrapper
1580 * functions below which are exported to rest of module.
1582 template<typename PIXEL>
1583 inline void PointSampleAddressablePixels(const uint8_t* inPixels,
1584 unsigned int inputWidth,
1585 unsigned int inputHeight,
1587 unsigned int desiredWidth,
1588 unsigned int desiredHeight)
1590 DALI_ASSERT_DEBUG(((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
1591 outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
1592 "The input and output buffers must not overlap for an upscaling.");
1593 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, ...).");
1594 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, ...).");
1596 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1600 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1601 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1602 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1603 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1605 unsigned int inY = 0;
1606 for(unsigned int outY = 0; outY < desiredHeight; ++outY)
1608 // Round fixed point y coordinate to nearest integer:
1609 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1610 const PIXEL* const inScanline = &inAligned[inputWidth * integerY];
1611 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1613 DALI_ASSERT_DEBUG(integerY < inputHeight);
1614 DALI_ASSERT_DEBUG(reinterpret_cast<const uint8_t*>(inScanline) < (inPixels + inputWidth * inputHeight * sizeof(PIXEL)));
1615 DALI_ASSERT_DEBUG(reinterpret_cast<uint8_t*>(outScanline) < (outPixels + desiredWidth * desiredHeight * sizeof(PIXEL)));
1617 unsigned int inX = 0;
1618 for(unsigned int outX = 0; outX < desiredWidth; ++outX)
1620 // Round the fixed-point x coordinate to an integer:
1621 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1622 const PIXEL* const inPixelAddress = &inScanline[integerX];
1623 const PIXEL pixel = *inPixelAddress;
1624 outScanline[outX] = pixel;
1634 void PointSample4BPP(const unsigned char* inPixels,
1635 unsigned int inputWidth,
1636 unsigned int inputHeight,
1637 unsigned char* outPixels,
1638 unsigned int desiredWidth,
1639 unsigned int desiredHeight)
1641 PointSampleAddressablePixels<uint32_t>(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1645 void PointSample2BPP(const unsigned char* inPixels,
1646 unsigned int inputWidth,
1647 unsigned int inputHeight,
1648 unsigned char* outPixels,
1649 unsigned int desiredWidth,
1650 unsigned int desiredHeight)
1652 PointSampleAddressablePixels<uint16_t>(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1656 void PointSample1BPP(const unsigned char* inPixels,
1657 unsigned int inputWidth,
1658 unsigned int inputHeight,
1659 unsigned char* outPixels,
1660 unsigned int desiredWidth,
1661 unsigned int desiredHeight)
1663 PointSampleAddressablePixels<uint8_t>(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1667 * RGB888 is a special case as its pixels are not aligned addressable units.
1669 void PointSample3BPP(const uint8_t* inPixels,
1670 unsigned int inputWidth,
1671 unsigned int inputHeight,
1673 unsigned int desiredWidth,
1674 unsigned int desiredHeight)
1676 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1680 const unsigned int BYTES_PER_PIXEL = 3;
1682 // Generate fixed-point 16.16 deltas in input image coordinates:
1683 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1684 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1686 // Step through output image in whole integer pixel steps while tracking the
1687 // corresponding locations in the input image using 16.16 fixed-point
1689 unsigned int inY = 0; //< 16.16 fixed-point input image y-coord.
1690 for(unsigned int outY = 0; outY < desiredHeight; ++outY)
1692 const unsigned int integerY = (inY + (1u << 15u)) >> 16u;
1693 const uint8_t* const inScanline = &inPixels[inputWidth * integerY * BYTES_PER_PIXEL];
1694 uint8_t* const outScanline = &outPixels[desiredWidth * outY * BYTES_PER_PIXEL];
1695 unsigned int inX = 0; //< 16.16 fixed-point input image x-coord.
1697 for(unsigned int outX = 0; outX < desiredWidth * BYTES_PER_PIXEL; outX += BYTES_PER_PIXEL)
1699 // Round the fixed-point input coordinate to the address of the input pixel to sample:
1700 const unsigned int integerX = (inX + (1u << 15u)) >> 16u;
1701 const uint8_t* const inPixelAddress = &inScanline[integerX * BYTES_PER_PIXEL];
1703 // Issue loads for all pixel color components up-front:
1704 const unsigned int c0 = inPixelAddress[0];
1705 const unsigned int c1 = inPixelAddress[1];
1706 const unsigned int c2 = inPixelAddress[2];
1707 ///@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.
1709 // Output the pixel components:
1710 outScanline[outX] = static_cast<uint8_t>(c0);
1711 outScanline[outX + 1] = static_cast<uint8_t>(c1);
1712 outScanline[outX + 2] = static_cast<uint8_t>(c2);
1714 // Increment the fixed-point input coordinate:
1722 // Dispatch to a format-appropriate point sampling function:
1723 void PointSample(const unsigned char* inPixels,
1724 unsigned int inputWidth,
1725 unsigned int inputHeight,
1726 Pixel::Format pixelFormat,
1727 unsigned char* outPixels,
1728 unsigned int desiredWidth,
1729 unsigned int desiredHeight)
1731 // Check the pixel format is one that is supported:
1732 if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1734 if(pixelFormat == Pixel::RGB888)
1736 PointSample3BPP(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1738 else if(pixelFormat == Pixel::RGBA8888)
1740 PointSample4BPP(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1742 else if(pixelFormat == Pixel::RGB565 || pixelFormat == Pixel::LA88)
1744 PointSample2BPP(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1746 else if(pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
1748 PointSample1BPP(inPixels, inputWidth, inputHeight, outPixels, desiredWidth, desiredHeight);
1752 DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
1757 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not point sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat));
1761 // Linear sampling group below
1765 /** @brief Blend 4 pixels together using horizontal and vertical weights. */
1766 inline uint8_t BilinearFilter1BPPByte(uint8_t tl, uint8_t tr, uint8_t bl, uint8_t br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1768 return static_cast<uint8_t>(BilinearFilter1Component(tl, tr, bl, br, fractBlendHorizontal, fractBlendVertical));
1771 /** @copydoc BilinearFilter1BPPByte */
1772 inline Pixel2Bytes BilinearFilter2Bytes(Pixel2Bytes tl, Pixel2Bytes tr, Pixel2Bytes bl, Pixel2Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1775 pixel.l = static_cast<uint8_t>(BilinearFilter1Component(tl.l, tr.l, bl.l, br.l, fractBlendHorizontal, fractBlendVertical));
1776 pixel.a = static_cast<uint8_t>(BilinearFilter1Component(tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical));
1780 /** @copydoc BilinearFilter1BPPByte */
1781 inline Pixel3Bytes BilinearFilterRGB888(Pixel3Bytes tl, Pixel3Bytes tr, Pixel3Bytes bl, Pixel3Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1784 pixel.r = static_cast<uint8_t>(BilinearFilter1Component(tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical));
1785 pixel.g = static_cast<uint8_t>(BilinearFilter1Component(tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical));
1786 pixel.b = static_cast<uint8_t>(BilinearFilter1Component(tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical));
1790 /** @copydoc BilinearFilter1BPPByte */
1791 inline PixelRGB565 BilinearFilterRGB565(PixelRGB565 tl, PixelRGB565 tr, PixelRGB565 bl, PixelRGB565 br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1793 const PixelRGB565 pixel = static_cast<PixelRGB565>((BilinearFilter1Component(tl >> 11u, tr >> 11u, bl >> 11u, br >> 11u, fractBlendHorizontal, fractBlendVertical) << 11u) +
1794 (BilinearFilter1Component((tl >> 5u) & 63u, (tr >> 5u) & 63u, (bl >> 5u) & 63u, (br >> 5u) & 63u, fractBlendHorizontal, fractBlendVertical) << 5u) +
1795 BilinearFilter1Component(tl & 31u, tr & 31u, bl & 31u, br & 31u, fractBlendHorizontal, fractBlendVertical));
1799 /** @copydoc BilinearFilter1BPPByte */
1800 inline Pixel4Bytes BilinearFilter4Bytes(Pixel4Bytes tl, Pixel4Bytes tr, Pixel4Bytes bl, Pixel4Bytes br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical)
1803 pixel.r = static_cast<uint8_t>(BilinearFilter1Component(tl.r, tr.r, bl.r, br.r, fractBlendHorizontal, fractBlendVertical));
1804 pixel.g = static_cast<uint8_t>(BilinearFilter1Component(tl.g, tr.g, bl.g, br.g, fractBlendHorizontal, fractBlendVertical));
1805 pixel.b = static_cast<uint8_t>(BilinearFilter1Component(tl.b, tr.b, bl.b, br.b, fractBlendHorizontal, fractBlendVertical));
1806 pixel.a = static_cast<uint8_t>(BilinearFilter1Component(tl.a, tr.a, bl.a, br.a, fractBlendHorizontal, fractBlendVertical));
1811 * @brief Generic version of bilinear sampling image resize function.
1812 * @note Limited to one compilation unit and exposed through type-specific
1813 * wrapper functions below.
1817 PIXEL (*BilinearFilter)(PIXEL tl, PIXEL tr, PIXEL bl, PIXEL br, unsigned int fractBlendHorizontal, unsigned int fractBlendVertical),
1818 bool DEBUG_ASSERT_ALIGNMENT>
1819 inline void LinearSampleGeneric(const unsigned char* __restrict__ inPixels,
1820 ImageDimensions inputDimensions,
1821 unsigned char* __restrict__ outPixels,
1822 ImageDimensions desiredDimensions)
1824 const unsigned int inputWidth = inputDimensions.GetWidth();
1825 const unsigned int inputHeight = inputDimensions.GetHeight();
1826 const unsigned int desiredWidth = desiredDimensions.GetWidth();
1827 const unsigned int desiredHeight = desiredDimensions.GetHeight();
1829 DALI_ASSERT_DEBUG(((outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL)) ||
1830 (inPixels >= outPixels + desiredWidth * desiredHeight * sizeof(PIXEL))) &&
1831 "Input and output buffers cannot overlap.");
1832 if(DEBUG_ASSERT_ALIGNMENT)
1834 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, ...).");
1835 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, ...).");
1838 if(inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u)
1842 const PIXEL* const inAligned = reinterpret_cast<const PIXEL*>(inPixels);
1843 PIXEL* const outAligned = reinterpret_cast<PIXEL*>(outPixels);
1844 const unsigned int deltaX = (inputWidth << 16u) / desiredWidth;
1845 const unsigned int deltaY = (inputHeight << 16u) / desiredHeight;
1847 unsigned int inY = 0;
1848 for(unsigned int outY = 0; outY < desiredHeight; ++outY)
1850 PIXEL* const outScanline = &outAligned[desiredWidth * outY];
1852 // Find the two scanlines to blend and the weight to blend with:
1853 const unsigned int integerY1 = inY >> 16u;
1854 const unsigned int integerY2 = integerY1 >= inputHeight ? integerY1 : integerY1 + 1;
1855 const unsigned int inputYWeight = inY & 65535u;
1857 DALI_ASSERT_DEBUG(integerY1 < inputHeight);
1858 DALI_ASSERT_DEBUG(integerY2 < inputHeight);
1860 const PIXEL* const inScanline1 = &inAligned[inputWidth * integerY1];
1861 const PIXEL* const inScanline2 = &inAligned[inputWidth * integerY2];
1863 unsigned int inX = 0;
1864 for(unsigned int outX = 0; outX < desiredWidth; ++outX)
1866 // Work out the two pixel scanline offsets for this cluster of four samples:
1867 const unsigned int integerX1 = inX >> 16u;
1868 const unsigned int integerX2 = integerX1 >= inputWidth ? integerX1 : integerX1 + 1;
1870 // Execute the loads:
1871 const PIXEL pixel1 = inScanline1[integerX1];
1872 const PIXEL pixel2 = inScanline2[integerX1];
1873 const PIXEL pixel3 = inScanline1[integerX2];
1874 const PIXEL pixel4 = inScanline2[integerX2];
1875 ///@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.
1877 // Weighted bilinear filter:
1878 const unsigned int inputXWeight = inX & 65535u;
1879 outScanline[outX] = BilinearFilter(pixel1, pixel3, pixel2, pixel4, inputXWeight, inputYWeight);
1889 // Format-specific linear scaling instantiations:
1891 void LinearSample1BPP(const unsigned char* __restrict__ inPixels,
1892 ImageDimensions inputDimensions,
1893 unsigned char* __restrict__ outPixels,
1894 ImageDimensions desiredDimensions)
1896 LinearSampleGeneric<uint8_t, BilinearFilter1BPPByte, false>(inPixels, inputDimensions, outPixels, desiredDimensions);
1899 void LinearSample2BPP(const unsigned char* __restrict__ inPixels,
1900 ImageDimensions inputDimensions,
1901 unsigned char* __restrict__ outPixels,
1902 ImageDimensions desiredDimensions)
1904 LinearSampleGeneric<Pixel2Bytes, BilinearFilter2Bytes, true>(inPixels, inputDimensions, outPixels, desiredDimensions);
1907 void LinearSampleRGB565(const unsigned char* __restrict__ inPixels,
1908 ImageDimensions inputDimensions,
1909 unsigned char* __restrict__ outPixels,
1910 ImageDimensions desiredDimensions)
1912 LinearSampleGeneric<PixelRGB565, BilinearFilterRGB565, true>(inPixels, inputDimensions, outPixels, desiredDimensions);
1915 void LinearSample3BPP(const unsigned char* __restrict__ inPixels,
1916 ImageDimensions inputDimensions,
1917 unsigned char* __restrict__ outPixels,
1918 ImageDimensions desiredDimensions)
1920 LinearSampleGeneric<Pixel3Bytes, BilinearFilterRGB888, false>(inPixels, inputDimensions, outPixels, desiredDimensions);
1923 void LinearSample4BPP(const unsigned char* __restrict__ inPixels,
1924 ImageDimensions inputDimensions,
1925 unsigned char* __restrict__ outPixels,
1926 ImageDimensions desiredDimensions)
1928 LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>(inPixels, inputDimensions, outPixels, desiredDimensions);
1931 void Resample(const unsigned char* __restrict__ inPixels,
1932 ImageDimensions inputDimensions,
1933 unsigned char* __restrict__ outPixels,
1934 ImageDimensions desiredDimensions,
1935 Resampler::Filter filterType,
1939 // Got from the test.cpp of the ImageResampler lib.
1940 const float ONE_DIV_255 = 1.0f / 255.0f;
1941 const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
1942 const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
1943 const int ALPHA_CHANNEL = hasAlpha ? (numChannels - 1) : 0;
1945 static bool loadColorSpaces = true;
1946 static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
1947 static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
1949 if(loadColorSpaces) // Only create the color space conversions on the first execution
1951 loadColorSpaces = false;
1953 for(int i = 0; i <= MAX_UNSIGNED_CHAR; ++i)
1955 srgbToLinear[i] = pow(static_cast<float>(i) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA);
1958 const float invLinearToSrgbTableSize = 1.0f / static_cast<float>(LINEAR_TO_SRGB_TABLE_SIZE);
1959 const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
1961 for(int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i)
1963 int k = static_cast<int>(255.0f * pow(static_cast<float>(i) * invLinearToSrgbTableSize, invSourceGamma) + 0.5f);
1968 else if(k > MAX_UNSIGNED_CHAR)
1970 k = MAX_UNSIGNED_CHAR;
1972 linearToSrgb[i] = static_cast<unsigned char>(k);
1976 std::vector<Resampler*> resamplers(numChannels);
1977 std::vector<Vector<float>> samples(numChannels);
1979 const int srcWidth = inputDimensions.GetWidth();
1980 const int srcHeight = inputDimensions.GetHeight();
1981 const int dstWidth = desiredDimensions.GetWidth();
1982 const int dstHeight = desiredDimensions.GetHeight();
1984 // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
1985 // used for the other components (a memory and slight cache efficiency optimization).
1986 resamplers[0] = new Resampler(srcWidth,
1990 Resampler::BOUNDARY_CLAMP,
1991 0.0f, // sample_low,
1992 1.0f, // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
1993 filterType, // The type of filter.
1995 NULL, // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
1996 FILTER_SCALE, // src_x_ofs,
1997 FILTER_SCALE); // src_y_ofs. Offset input image by specified amount (fractional values okay).
1998 samples[0].ResizeUninitialized(srcWidth);
1999 for(int i = 1; i < numChannels; ++i)
2001 resamplers[i] = new Resampler(srcWidth,
2005 Resampler::BOUNDARY_CLAMP,
2009 resamplers[0]->get_clist_x(),
2010 resamplers[0]->get_clist_y(),
2013 samples[i].ResizeUninitialized(srcWidth);
2016 const int srcPitch = srcWidth * numChannels;
2017 const int dstPitch = dstWidth * numChannels;
2020 for(int srcY = 0; srcY < srcHeight; ++srcY)
2022 const unsigned char* pSrc = &inPixels[srcY * srcPitch];
2024 for(int x = 0; x < srcWidth; ++x)
2026 for(int c = 0; c < numChannels; ++c)
2028 if(c == ALPHA_CHANNEL && hasAlpha)
2030 samples[c][x] = *pSrc++ * ONE_DIV_255;
2034 samples[c][x] = srgbToLinear[*pSrc++];
2039 for(int c = 0; c < numChannels; ++c)
2041 if(!resamplers[c]->put_line(&samples[c][0]))
2043 DALI_ASSERT_DEBUG(!"Out of memory");
2050 for(compIndex = 0; compIndex < numChannels; ++compIndex)
2052 const float* pOutputSamples = resamplers[compIndex]->get_line();
2058 const bool isAlphaChannel = (compIndex == ALPHA_CHANNEL && hasAlpha);
2059 DALI_ASSERT_DEBUG(dstY < dstHeight);
2060 unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
2062 for(int x = 0; x < dstWidth; ++x)
2066 int c = static_cast<int>(255.0f * pOutputSamples[x] + 0.5f);
2071 else if(c > MAX_UNSIGNED_CHAR)
2073 c = MAX_UNSIGNED_CHAR;
2075 *pDst = static_cast<unsigned char>(c);
2079 int j = static_cast<int>(LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f);
2084 else if(j >= LINEAR_TO_SRGB_TABLE_SIZE)
2086 j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
2088 *pDst = linearToSrgb[j];
2091 pDst += numChannels;
2094 if(compIndex < numChannels)
2103 // Delete the resamplers.
2104 for(int i = 0; i < numChannels; ++i)
2106 delete resamplers[i];
2110 void LanczosSample4BPP(const unsigned char* __restrict__ inPixels,
2111 ImageDimensions inputDimensions,
2112 unsigned char* __restrict__ outPixels,
2113 ImageDimensions desiredDimensions)
2115 Resample(inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 4, true);
2118 void LanczosSample1BPP(const unsigned char* __restrict__ inPixels,
2119 ImageDimensions inputDimensions,
2120 unsigned char* __restrict__ outPixels,
2121 ImageDimensions desiredDimensions)
2124 Resample(inPixels, inputDimensions, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false);
2127 // Dispatch to a format-appropriate linear sampling function:
2128 void LinearSample(const unsigned char* __restrict__ inPixels,
2129 ImageDimensions inDimensions,
2130 Pixel::Format pixelFormat,
2131 unsigned char* __restrict__ outPixels,
2132 ImageDimensions outDimensions)
2134 // Check the pixel format is one that is supported:
2135 if(pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565)
2137 if(pixelFormat == Pixel::RGB888)
2139 LinearSample3BPP(inPixels, inDimensions, outPixels, outDimensions);
2141 else if(pixelFormat == Pixel::RGBA8888)
2143 LinearSample4BPP(inPixels, inDimensions, outPixels, outDimensions);
2145 else if(pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
2147 LinearSample1BPP(inPixels, inDimensions, outPixels, outDimensions);
2149 else if(pixelFormat == Pixel::LA88)
2151 LinearSample2BPP(inPixels, inDimensions, outPixels, outDimensions);
2153 else if(pixelFormat == Pixel::RGB565)
2155 LinearSampleRGB565(inPixels, inDimensions, outPixels, outDimensions);
2159 DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
2164 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat));
2168 void RotateByShear(const uint8_t* const pixelsIn,
2169 unsigned int widthIn,
2170 unsigned int heightIn,
2171 unsigned int pixelSize,
2173 uint8_t*& pixelsOut,
2174 unsigned int& widthOut,
2175 unsigned int& heightOut)
2177 // @note Code got from https://www.codeproject.com/Articles/202/High-quality-image-rotation-rotate-by-shear by Eran Yariv.
2179 // Do first the fast rotations to transform the angle into a (-45..45] range.
2181 float fastRotationPerformed = false;
2182 if((radians > Math::PI_4) && (radians <= RAD_135))
2184 // Angle in (45.0 .. 135.0]
2185 // Rotate image by 90 degrees into temporary image,
2186 // so it requires only an extra rotation angle
2187 // of -45.0 .. +45.0 to complete rotation.
2188 fastRotationPerformed = Rotate90(pixelsIn,
2196 if(!fastRotationPerformed)
2198 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2199 // The fast rotation failed.
2203 radians -= Math::PI_2;
2205 else if((radians > RAD_135) && (radians <= RAD_225))
2207 // Angle in (135.0 .. 225.0]
2208 // Rotate image by 180 degrees into temporary image,
2209 // so it requires only an extra rotation angle
2210 // of -45.0 .. +45.0 to complete rotation.
2212 fastRotationPerformed = Rotate180(pixelsIn,
2218 if(!fastRotationPerformed)
2220 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2221 // The fast rotation failed.
2225 radians -= Math::PI;
2227 heightOut = heightIn;
2229 else if((radians > RAD_225) && (radians <= RAD_315))
2231 // Angle in (225.0 .. 315.0]
2232 // Rotate image by 270 degrees into temporary image,
2233 // so it requires only an extra rotation angle
2234 // of -45.0 .. +45.0 to complete rotation.
2236 fastRotationPerformed = Rotate270(pixelsIn,
2244 if(!fastRotationPerformed)
2246 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "fast rotation failed\n");
2247 // The fast rotation failed.
2254 if(fabs(radians) < Dali::Math::MACHINE_EPSILON_10)
2256 // Nothing else to do if the angle is zero.
2257 // The rotation angle was 90, 180 or 270.
2259 // @note Allocated memory by 'Fast Rotations', if any, has to be freed by the called to this function.
2263 const uint8_t* const firstHorizontalSkewPixelsIn = fastRotationPerformed ? pixelsOut : pixelsIn;
2264 std::unique_ptr<uint8_t, void (*)(void*)> tmpPixelsInPtr((fastRotationPerformed ? pixelsOut : nullptr), free);
2266 // Reset the input/output
2268 heightIn = heightOut;
2269 pixelsOut = nullptr;
2271 const float angleSinus = sin(radians);
2272 const float angleCosinus = cos(radians);
2273 const float angleTangent = tan(0.5f * radians);
2275 ///////////////////////////////////////
2276 // Perform 1st shear (horizontal)
2277 ///////////////////////////////////////
2279 // Calculate first shear (horizontal) destination image dimensions
2281 widthOut = widthIn + static_cast<unsigned int>(fabs(angleTangent) * static_cast<float>(heightIn));
2282 heightOut = heightIn;
2284 // Allocate the buffer for the 1st shear
2285 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2287 if(nullptr == pixelsOut)
2292 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2294 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Fast rotations'.
2295 // Nothing else to do if the memory allocation fails.
2299 for(unsigned int y = 0u; y < heightOut; ++y)
2301 const float shear = angleTangent * ((angleTangent >= 0.f) ? (0.5f + static_cast<float>(y)) : (0.5f + static_cast<float>(y) - static_cast<float>(heightOut)));
2303 const int intShear = static_cast<int>(floor(shear));
2304 HorizontalSkew(firstHorizontalSkewPixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>(intShear));
2307 // Reset the 'pixel in' pointer with the output of the 'First Horizontal Skew' and free the memory allocated by the 'Fast Rotations'.
2308 tmpPixelsInPtr.reset(pixelsOut);
2309 unsigned int tmpWidthIn = widthOut;
2310 unsigned int tmpHeightIn = heightOut;
2312 // Reset the input/output
2313 pixelsOut = nullptr;
2315 ///////////////////////////////////////
2316 // Perform 2nd shear (vertical)
2317 ///////////////////////////////////////
2319 // Calc 2nd shear (vertical) destination image dimensions
2320 heightOut = static_cast<unsigned int>(static_cast<float>(widthIn) * fabs(angleSinus) + static_cast<float>(heightIn) * angleCosinus);
2322 // Allocate the buffer for the 2nd shear
2323 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2325 if(nullptr == pixelsOut)
2330 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2331 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'First Horizontal Skew'.
2332 // Nothing else to do if the memory allocation fails.
2336 // Variable skew offset
2337 float offset = angleSinus * ((angleSinus > 0.f) ? static_cast<float>(widthIn - 1u) : -(static_cast<float>(widthIn) - static_cast<float>(widthOut)));
2339 unsigned int column = 0u;
2340 for(column = 0u; column < widthOut; ++column, offset -= angleSinus)
2342 const int shear = static_cast<int>(floor(offset));
2343 VerticalSkew(tmpPixelsInPtr.get(), tmpWidthIn, tmpHeightIn, pixelSize, pixelsOut, widthOut, heightOut, column, shear, offset - static_cast<float>(shear));
2345 // Reset the 'pixel in' pointer with the output of the 'Vertical Skew' and free the memory allocated by the 'First Horizontal Skew'.
2346 // Reset the input/output
2347 tmpPixelsInPtr.reset(pixelsOut);
2348 tmpWidthIn = widthOut;
2349 tmpHeightIn = heightOut;
2350 pixelsOut = nullptr;
2352 ///////////////////////////////////////
2353 // Perform 3rd shear (horizontal)
2354 ///////////////////////////////////////
2356 // Calc 3rd shear (horizontal) destination image dimensions
2357 widthOut = static_cast<unsigned int>(static_cast<float>(heightIn) * fabs(angleSinus) + static_cast<float>(widthIn) * angleCosinus) + 1u;
2359 // Allocate the buffer for the 3rd shear
2360 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2362 if(nullptr == pixelsOut)
2367 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2368 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2369 // Nothing else to do if the memory allocation fails.
2373 offset = (angleSinus >= 0.f) ? -angleSinus * angleTangent * static_cast<float>(widthIn - 1u) : angleTangent * (static_cast<float>(widthIn - 1u) * -angleSinus + (1.f - static_cast<float>(heightOut)));
2375 for(unsigned int y = 0u; y < heightOut; ++y, offset += angleTangent)
2377 const int shear = static_cast<int>(floor(offset));
2378 HorizontalSkew(tmpPixelsInPtr.get(), tmpWidthIn, pixelSize, pixelsOut, widthOut, y, shear, offset - static_cast<float>(shear));
2381 // The deleter of the tmpPixelsInPtr unique pointer is called freeing the memory allocated by the 'Vertical Skew'.
2382 // @note Allocated memory by the last 'Horizontal Skew' has to be freed by the caller to this function.
2385 void HorizontalShear(const uint8_t* const pixelsIn,
2386 unsigned int widthIn,
2387 unsigned int heightIn,
2388 unsigned int pixelSize,
2390 uint8_t*& pixelsOut,
2391 unsigned int& widthOut,
2392 unsigned int& heightOut)
2394 // Calculate the destination image dimensions.
2396 const float absRadians = fabs(radians);
2398 if(absRadians > Math::PI_4)
2400 // Can't shear more than 45 degrees.
2404 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Can't shear more than 45 degrees (PI/4 radians). radians : %f\n", radians);
2408 widthOut = widthIn + static_cast<unsigned int>(ceil(absRadians * static_cast<float>(heightIn)));
2409 heightOut = heightIn;
2411 // Allocate the buffer for the shear.
2412 pixelsOut = static_cast<uint8_t*>(malloc(widthOut * heightOut * pixelSize));
2414 if(nullptr == pixelsOut)
2419 DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "malloc failed to allocate memory\n");
2423 for(unsigned int y = 0u; y < heightOut; ++y)
2425 const float shear = radians * ((radians >= 0.f) ? (0.5f + static_cast<float>(y)) : (0.5f + static_cast<float>(y) - static_cast<float>(heightOut)));
2427 const int intShear = static_cast<int>(floor(shear));
2428 HorizontalSkew(pixelsIn, widthIn, pixelSize, pixelsOut, widthOut, y, intShear, shear - static_cast<float>(intShear));
2432 } /* namespace Platform */
2433 } /* namespace Internal */
2434 } /* namespace Dali */