- If image size is smaller than 2, or exactly 65535, internal logic breakdown.
Let we guard that case so we can render something without error.
- If border is bigger than original size, stretch range logic become breakdown.
Let we make helper util to make ensure the stretch range is valid
even if we use border value extreamly high.
Change-Id: I7e951af9a8bb17c22ad71bf7db4ba25e553e3935
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
/*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <dali-toolkit/devel-api/utility/npatch-utilities.h>
#include <stdlib.h>
#include <iostream>
+#include <utility>
+#include <vector>
using namespace Dali;
using namespace Dali::Toolkit;
}
}
-Dali::Devel::PixelBuffer CustomizeNPatch(uint32_t width, uint32_t height, const Vector4& requiredStretchBorder)
+Dali::Devel::PixelBuffer CreateEmptyPixelBuffer(uint32_t width, uint32_t height, Pixel::Format pixelFormat)
{
- Pixel::Format pixelFormat = Pixel::RGBA8888;
Dali::Devel::PixelBuffer pixelBuffer = Dali::Devel::PixelBuffer::New(width, height, pixelFormat);
unsigned char* buffer = pixelBuffer.GetBuffer();
memset(buffer, 0, width * height * Dali::Pixel::GetBytesPerPixel(pixelFormat));
+ return pixelBuffer;
+}
+
+Dali::Devel::PixelBuffer CustomizeNPatch(uint32_t width, uint32_t height, const Vector4& requiredStretchBorder)
+{
+ Pixel::Format pixelFormat = Pixel::RGBA8888;
+ Dali::Devel::PixelBuffer pixelBuffer = CreateEmptyPixelBuffer(width, height, pixelFormat);
+
InitialiseRegionsToZeroAlpha(pixelBuffer, width, height, pixelFormat);
AddStretchRegionsToImage(pixelBuffer, width, height, requiredStretchBorder, pixelFormat);
tet_infoline("UtcDaliNPatchUtilityParseBorders");
/* Stretch region left(2) top(2) right (2) bottom (2)
- * ss
- * OOOOOO
- * OOOOOOc
- * sOOooOOc
- * sOOooOOc
- * OOOOOOc
- * OOOOOO
- * cccc
- */
+ * ss
+ * OOOOOO
+ * OOOOOOc
+ * sOOooOOc
+ * sOOooOOc
+ * OOOOOOc
+ * OOOOOO
+ * cccc
+ */
const unsigned int imageHeight = 18;
const unsigned int imageWidth = 28;
NPatchUtility::StretchRanges stretchPixelsX;
NPatchUtility::StretchRanges stretchPixelsY;
- NPatchUtility::ParseBorders(pixelBuffer, stretchPixelsX, stretchPixelsY);
+ bool ret = NPatchUtility::ParseBorders(pixelBuffer, stretchPixelsX, stretchPixelsY);
+ DALI_TEST_CHECK(ret);
DALI_TEST_CHECK(stretchPixelsX.Size() == 1);
DALI_TEST_CHECK(stretchPixelsY.Size() == 1);
END_TEST;
}
+int UtcDaliNPatchUtilityParseBordersN(void)
+{
+ TestApplication application;
+ tet_infoline("UtcDaliNPatchUtilityParseBordersN");
+
+ for(const std::pair<uint32_t, uint32_t>& imageSizePair : std::initializer_list<std::pair<uint32_t, uint32_t>>({{1u, 1u}, {2u, 2u}, {0xFFFF, 0xFFFF}, {2u, 129u}}))
+ {
+ tet_printf("Parse for image size : %u x %u\n", imageSizePair.first, imageSizePair.second);
+ Dali::Devel::PixelBuffer pixelBuffer = CreateEmptyPixelBuffer(imageSizePair.first, imageSizePair.second, Pixel::RGBA8888);
+ DALI_TEST_CHECK(pixelBuffer);
+
+ if(pixelBuffer)
+ {
+ NPatchUtility::StretchRanges stretchPixelsX;
+ NPatchUtility::StretchRanges stretchPixelsY;
+
+ bool ret = NPatchUtility::ParseBorders(pixelBuffer, stretchPixelsX, stretchPixelsY);
+
+ DALI_TEST_CHECK(!ret);
+ DALI_TEST_CHECK(stretchPixelsX.Size() == 0);
+ DALI_TEST_CHECK(stretchPixelsY.Size() == 0);
+ }
+ else
+ {
+ test_return_value = TET_FAIL;
+ }
+ }
+
+ END_TEST;
+}
+
int UtcDaliNPatchUtilityIsNinePatchUrl(void)
{
tet_infoline("UtcDaliNPatchUtilityIsNinePatchUrl");
END_TEST;
}
+
+int UtcDaliNPatchUtilityGetValidStrechPointFromBorder(void)
+{
+ tet_infoline("UtcDaliNPatchUtilityGetValidStrechPointFromBorder");
+
+ Uint16Pair value;
+ value = NPatchUtility::GetValidStrechPointFromBorder(200u, 50u, 30u);
+ DALI_TEST_EQUALS(value.GetX(), 50u, TEST_LOCATION);
+ DALI_TEST_EQUALS(value.GetY(), 170u, TEST_LOCATION);
+
+ value = NPatchUtility::GetValidStrechPointFromBorder(80u, 50u, 30u);
+ DALI_TEST_EQUALS(value.GetX(), 50u, TEST_LOCATION);
+ DALI_TEST_EQUALS(value.GetY(), 50u, TEST_LOCATION);
+
+ tet_printf("Check the ratio of input keep ratio or not\n");
+ value = NPatchUtility::GetValidStrechPointFromBorder(8u, 50u, 30u);
+ DALI_TEST_EQUALS(value.GetX(), 5u, TEST_LOCATION);
+ DALI_TEST_EQUALS(value.GetY(), 5u, TEST_LOCATION);
+
+ value = NPatchUtility::GetValidStrechPointFromBorder(8u, 50u, 50u);
+ DALI_TEST_EQUALS(value.GetX(), 4u, TEST_LOCATION);
+ DALI_TEST_EQUALS(value.GetY(), 4u, TEST_LOCATION);
+
+ tet_printf("Check the ratio is not fit with given value\n");
+ value = NPatchUtility::GetValidStrechPointFromBorder(9u, 50u, 50u);
+ DALI_TEST_EQUALS(value.GetX(), value.GetY(), TEST_LOCATION);
+
+ tet_printf("Check the input size overflowed uint16_t\n");
+ value = NPatchUtility::GetValidStrechPointFromBorder(0xFFFFFFu, 0xFFFFFFu, 0xFFFFFFu);
+ DALI_TEST_EQUALS(value.GetX(), 0xFFFFu / 2, TEST_LOCATION);
+ DALI_TEST_EQUALS(value.GetY(), 0xFFFFu / 2, TEST_LOCATION);
+
+ value = NPatchUtility::GetValidStrechPointFromBorder(0xFFFFFFu, 4u, 8u);
+ DALI_TEST_EQUALS(value.GetX(), 4u, TEST_LOCATION);
+ DALI_TEST_EQUALS(value.GetY(), 0xFFFFu - 8u, TEST_LOCATION);
+
+ value = NPatchUtility::GetValidStrechPointFromBorder(8u, 0xFFFFFFu, 0xFFFFFFu);
+ DALI_TEST_EQUALS(value.GetX(), 4u, TEST_LOCATION);
+ DALI_TEST_EQUALS(value.GetY(), 4u, TEST_LOCATION);
+
+ END_TEST;
+}
/*
-* Copyright (c) 2022 Samsung Electronics Co., Ltd.
+* Copyright (c) 2025 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
}
}
-void ParseBorders(Devel::PixelBuffer& pixelBuffer, StretchRanges& stretchPixelsX, StretchRanges& stretchPixelsY)
+bool ParseBorders(Devel::PixelBuffer& pixelBuffer, StretchRanges& stretchPixelsX, StretchRanges& stretchPixelsY)
{
stretchPixelsX.Clear();
stretchPixelsY.Clear();
uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(pixelFormat);
uint32_t width = pixelBuffer.GetWidth();
uint32_t height = pixelBuffer.GetHeight();
+ uint32_t srcStride = pixelBuffer.GetStrideBytes() ? pixelBuffer.GetStrideBytes() : width * bytesPerPixel;
uint8_t* srcPixels = pixelBuffer.GetBuffer();
- uint32_t srcStride = width * bytesPerPixel;
+
+ if(width <= 2 || width >= 0xFFFF || height <= 2 || height >= 0xFFFF)
+ {
+ DALI_LOG_ERROR("PixelBuffer size not allowed! [%u x %u] border parsing failed\n", width, height);
+ return false;
+ }
// TOP
uint8_t* top = srcPixels + bytesPerPixel;
{
stretchPixelsY.PushBack(Uint16Pair(0, height - 2));
}
+
+ return true;
}
bool IsNinePatchUrl(const std::string& url)
return match;
}
+Dali::Uint16Pair GetValidStrechPointFromBorder(uint32_t maxRangeSize, uint32_t rangeFromZero, uint32_t rangeFromMax)
+{
+ maxRangeSize = std::min(maxRangeSize, 0xFFFFu);
+ rangeFromZero = std::min(rangeFromZero, 0xFFFFu);
+ rangeFromMax = std::min(rangeFromMax, 0xFFFFu);
+ if(DALI_UNLIKELY(rangeFromZero + rangeFromMax > maxRangeSize))
+ {
+ // Keep ratio and make ensure that sume of value didn't overflow the max range.
+ // Note that we can assume that rangeSum is bigger than zero!
+ uint32_t rangeSum = rangeFromZero + rangeFromMax;
+
+ rangeFromZero = (rangeFromZero * maxRangeSize) / rangeSum;
+ rangeFromMax = (rangeFromMax * maxRangeSize) / rangeSum;
+
+ // Ensure to make rangeFromZero + rangeFromMax is equal to maxRangeSize.
+ uint32_t remainedRange = maxRangeSize - (rangeFromZero + rangeFromMax);
+ rangeFromZero += (remainedRange / 2);
+ rangeFromMax += remainedRange - (remainedRange / 2);
+ }
+
+ DALI_ASSERT_DEBUG(rangeFromZero + rangeFromMax <= maxRangeSize && "Rearrange strech point failed!");
+
+ return Uint16Pair(rangeFromZero, maxRangeSize - rangeFromMax);
+}
+
} // namespace NPatchUtility
} // namespace Toolkit
#define DALI_TOOLKIT_NPATCH_UTILITIES_H
/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* @param[in] pixelBuffer The npatch image buffer.
* @param[out] stretchPixelsX The horizontal stretchable pixels in the cropped image space.
* @param[out] stretchPixelsY The vertical stretchable pixels in the cropped image space.
+ * @return True if parse success. False otherwise. If parse failed, strechPixels become empty.
*/
-DALI_TOOLKIT_API void ParseBorders(Devel::PixelBuffer& pixelBuffer, StretchRanges& stretchPixelsX, StretchRanges& stretchPixelsY);
+DALI_TOOLKIT_API bool ParseBorders(Devel::PixelBuffer& pixelBuffer, StretchRanges& stretchPixelsX, StretchRanges& stretchPixelsY);
/**
* @brief Helper method to determine if the filename indicates that the image has a 9 patch or n patch border.
*/
DALI_TOOLKIT_API bool IsNinePatchUrl(const std::string& url);
+/**
+ * @brief Calculate valid stretch range from given values.
+ *
+ * @param[in] maxRangeSize The maximum point of strech range.
+ * @param[in] rangeFromZero Distance from zero point.
+ * @param[in] rangeFromMax Distance from maximum range point.
+ * @return Calculated valid range from given input.
+ */
+DALI_TOOLKIT_API Uint16Pair GetValidStrechPointFromBorder(uint32_t maxRangeSize, uint32_t rangeFromZero, uint32_t rangeFromMax);
+
} // namespace NPatchUtility
} // namespace Toolkit
/*
- * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
void NPatchData::SetLoadedNPatchData(Devel::PixelBuffer& pixelBuffer, bool preMultiplied)
{
- if(mBorder == Rect<int>(0, 0, 0, 0))
+ if(mBorder == Rect<int>(0, 0, 0, 0) && NPatchUtility::ParseBorders(pixelBuffer, mStretchPixelsX, mStretchPixelsY))
{
- NPatchUtility::ParseBorders(pixelBuffer, mStretchPixelsX, mStretchPixelsY);
-
// Crop the image
pixelBuffer.Crop(1, 1, pixelBuffer.GetWidth() - 2, pixelBuffer.GetHeight() - 2);
}
else
{
- mStretchPixelsX.PushBack(Uint16Pair(mBorder.left, ((pixelBuffer.GetWidth() >= static_cast<unsigned int>(mBorder.right)) ? pixelBuffer.GetWidth() - mBorder.right : 0)));
- mStretchPixelsY.PushBack(Uint16Pair(mBorder.top, ((pixelBuffer.GetHeight() >= static_cast<unsigned int>(mBorder.bottom)) ? pixelBuffer.GetHeight() - mBorder.bottom : 0)));
+ mStretchPixelsX.PushBack(NPatchUtility::GetValidStrechPointFromBorder(pixelBuffer.GetWidth(), static_cast<uint32_t>(mBorder.left), static_cast<uint32_t>(mBorder.right)));
+ mStretchPixelsY.PushBack(NPatchUtility::GetValidStrechPointFromBorder(pixelBuffer.GetHeight(), static_cast<uint32_t>(mBorder.top), static_cast<uint32_t>(mBorder.bottom)));
}
mCroppedWidth = pixelBuffer.GetWidth();
info.mData->SetTextures(infoPtr->mData->GetTextures());
NPatchUtility::StretchRanges stretchRangesX;
- stretchRangesX.PushBack(Uint16Pair(border.left, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.right)) ? info.mData->GetCroppedWidth() - border.right : 0)));
-
NPatchUtility::StretchRanges stretchRangesY;
- stretchRangesY.PushBack(Uint16Pair(border.top, ((info.mData->GetCroppedHeight() >= static_cast<unsigned int>(border.bottom)) ? info.mData->GetCroppedHeight() - border.bottom : 0)));
+ stretchRangesX.PushBack(NPatchUtility::GetValidStrechPointFromBorder(info.mData->GetCroppedWidth(), static_cast<uint32_t>(border.left), static_cast<uint32_t>(border.right)));
+ stretchRangesY.PushBack(NPatchUtility::GetValidStrechPointFromBorder(info.mData->GetCroppedHeight(), static_cast<uint32_t>(border.top), static_cast<uint32_t>(border.bottom)));
info.mData->SetStretchPixelsX(stretchRangesX);
info.mData->SetStretchPixelsY(stretchRangesY);
}
Property::Value* borderValue = propertyMap.Find(Toolkit::ImageVisual::Property::BORDER, BORDER);
- if(borderValue && !borderValue->Get(mBorder)) // If value exists and is rect, just set mBorder
+ if(borderValue)
{
- // Not a rect so try vector4
- Vector4 border;
- if(borderValue->Get(border))
+ if(!borderValue->Get(mBorder)) // If value exists and is rect, just set mBorder
{
- mBorder.left = static_cast<int>(border.x);
- mBorder.right = static_cast<int>(border.y);
- mBorder.bottom = static_cast<int>(border.z);
- mBorder.top = static_cast<int>(border.w);
+ // Not a rect so try vector4
+ Vector4 border;
+ if(borderValue->Get(border))
+ {
+ mBorder.left = static_cast<int>(border.x);
+ mBorder.right = static_cast<int>(border.y);
+ mBorder.bottom = static_cast<int>(border.z);
+ mBorder.top = static_cast<int>(border.w);
+ }
}
+ // Ensure the range of border valid.
+ Dali::ClampInPlace(mBorder.left, 0, 0xFFFF);
+ Dali::ClampInPlace(mBorder.right, 0, 0xFFFF);
+ Dali::ClampInPlace(mBorder.bottom, 0, 0xFFFF);
+ Dali::ClampInPlace(mBorder.top, 0, 0xFFFF);
}
Property::Value* auxImage = propertyMap.Find(Toolkit::DevelImageVisual::Property::AUXILIARY_IMAGE, AUXILIARY_IMAGE_NAME);