(NPatch) Fix logical error case when decode npatch data 78/320978/1
authorEunki, Hong <eunkiki.hong@samsung.com>
Wed, 12 Mar 2025 08:22:00 +0000 (17:22 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Wed, 12 Mar 2025 08:56:02 +0000 (17:56 +0900)
- 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>
automated-tests/src/dali-toolkit/utc-Dali-NPatchUtilities.cpp
dali-toolkit/devel-api/utility/npatch-utilities.cpp
dali-toolkit/devel-api/utility/npatch-utilities.h
dali-toolkit/internal/visuals/npatch/npatch-data.cpp
dali-toolkit/internal/visuals/npatch/npatch-loader.cpp
dali-toolkit/internal/visuals/npatch/npatch-visual.cpp

index 4b7e6f80dde5b68484eed0102fb09e8b3833189a..09cb72fb23c8a5d3b28d6757b39028694e261bf2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -19,6 +19,8 @@
 #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;
@@ -71,14 +73,21 @@ void AddStretchRegionsToImage(Dali::Devel::PixelBuffer pixelBuffer, uint32_t wid
   }
 }
 
-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);
@@ -243,15 +252,15 @@ int UtcDaliNPatchUtilityParseBorders(void)
   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;
@@ -265,8 +274,9 @@ int UtcDaliNPatchUtilityParseBorders(void)
     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);
 
@@ -287,6 +297,37 @@ int UtcDaliNPatchUtilityParseBorders(void)
   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");
@@ -299,3 +340,45 @@ int UtcDaliNPatchUtilityIsNinePatchUrl(void)
 
   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;
+}
index aead2a51d45656ced2ec4dd660d208571c9b2423..26f5bc701f9da9ef370dccff99e0cdc4b670e10d 100644 (file)
@@ -1,5 +1,5 @@
 /*
-* 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.
@@ -187,7 +187,7 @@ void GetRedOffsetAndMask(Dali::Pixel::Format pixelFormat, int32_t& byteOffset, i
   }
 }
 
-void ParseBorders(Devel::PixelBuffer& pixelBuffer, StretchRanges& stretchPixelsX, StretchRanges& stretchPixelsY)
+bool ParseBorders(Devel::PixelBuffer& pixelBuffer, StretchRanges& stretchPixelsX, StretchRanges& stretchPixelsY)
 {
   stretchPixelsX.Clear();
   stretchPixelsY.Clear();
@@ -210,8 +210,14 @@ void ParseBorders(Devel::PixelBuffer& pixelBuffer, StretchRanges& stretchPixelsX
   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;
@@ -247,6 +253,8 @@ void ParseBorders(Devel::PixelBuffer& pixelBuffer, StretchRanges& stretchPixelsX
   {
     stretchPixelsY.PushBack(Uint16Pair(0, height - 2));
   }
+
+  return true;
 }
 
 bool IsNinePatchUrl(const std::string& url)
@@ -315,6 +323,31 @@ 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
index c4f055c80b18a94f2500d77d7c2829f1ffe0de7d..194d5ae75d9d6056092e7e4567379040719831ea 100644 (file)
@@ -2,7 +2,7 @@
 #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.
@@ -52,8 +52,9 @@ DALI_TOOLKIT_API void GetRedOffsetAndMask(Dali::Pixel::Format pixelFormat, int&
  * @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.
@@ -63,6 +64,16 @@ DALI_TOOLKIT_API void ParseBorders(Devel::PixelBuffer& pixelBuffer, StretchRange
  */
 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
index 50c425486581a5c7da72ce7d77b832d281309856..75dbced44705ef1d239892ed8d24dc78a3ce3474 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -211,17 +211,15 @@ void* NPatchData::GetRenderingMap() const
 
 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();
index e4d35acef21b1e40815a2c3bb35f1cd276a31d90..a02fe69ca7bd97c0b5caa1039662b756bd41b054 100644 (file)
@@ -276,10 +276,9 @@ NPatchDataPtr NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>&
     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);
index fec22bef143c056b94db8747612d9f0a515c4a60..f5ec71128db74f4346969234ec81de409e55c7e9 100644 (file)
@@ -167,17 +167,25 @@ void NPatchVisual::DoSetProperties(const Property::Map& propertyMap)
   }
 
   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);