From c2375c78a69412e297bc997be798122fe4e9675f Mon Sep 17 00:00:00 2001 From: Pasukhin Dmitry Date: Mon, 17 Nov 2025 09:54:17 +0000 Subject: [PATCH] Foundation Classes - Optimize Quantity package (#834) - Converted validation macros to inline functions for better type safety - Added `noexcept` specifiers to non-throwing functions for compiler optimization opportunities - Added `constexpr` to compile-time evaluable functions (comparison operators, leap year calculation) - Enhanced `Quantity_Color::StringName()` to return "UNDEFINED" instead of throwing exceptions - Introduced shared time constants header for better maintainability --- .../TKernel/GTests/FILES.cmake | 4 + .../GTests/Quantity_ColorRGBA_Test.cxx | 285 ++++++++++++ .../TKernel/GTests/Quantity_Color_Test.cxx | 293 +++++++++++++ .../TKernel/GTests/Quantity_Date_Test.cxx | 409 ++++++++++++++++++ .../TKernel/GTests/Quantity_Period_Test.cxx | 393 +++++++++++++++++ .../TKernel/Quantity/Quantity_Color.cxx | 231 ++++++---- .../TKernel/Quantity/Quantity_Color.hxx | 90 ++-- .../TKernel/Quantity/Quantity_ColorRGBA.cxx | 14 +- .../TKernel/Quantity/Quantity_ColorRGBA.hxx | 42 +- .../TKernel/Quantity/Quantity_Date.cxx | 231 ++++------ .../TKernel/Quantity/Quantity_Date.hxx | 32 +- .../TKernel/Quantity/Quantity_Period.cxx | 96 +--- .../TKernel/Quantity/Quantity_Period.hxx | 32 +- .../Quantity/Quantity_TimeConstants.pxx | 96 ++++ 14 files changed, 1854 insertions(+), 394 deletions(-) create mode 100644 src/FoundationClasses/TKernel/GTests/Quantity_ColorRGBA_Test.cxx create mode 100644 src/FoundationClasses/TKernel/GTests/Quantity_Color_Test.cxx create mode 100644 src/FoundationClasses/TKernel/GTests/Quantity_Date_Test.cxx create mode 100644 src/FoundationClasses/TKernel/GTests/Quantity_Period_Test.cxx create mode 100644 src/FoundationClasses/TKernel/Quantity/Quantity_TimeConstants.pxx diff --git a/src/FoundationClasses/TKernel/GTests/FILES.cmake b/src/FoundationClasses/TKernel/GTests/FILES.cmake index 582fc9d16b..5bc29c2ab9 100644 --- a/src/FoundationClasses/TKernel/GTests/FILES.cmake +++ b/src/FoundationClasses/TKernel/GTests/FILES.cmake @@ -22,6 +22,10 @@ set(OCCT_TKernel_GTests_FILES NCollection_Vector_Test.cxx OSD_Path_Test.cxx OSD_PerfMeter_Test.cxx + Quantity_Color_Test.cxx + Quantity_ColorRGBA_Test.cxx + Quantity_Date_Test.cxx + Quantity_Period_Test.cxx Standard_ArrayStreamBuffer_Test.cxx Standard_Atomic_Test.cxx Standard_Character_Test.cxx diff --git a/src/FoundationClasses/TKernel/GTests/Quantity_ColorRGBA_Test.cxx b/src/FoundationClasses/TKernel/GTests/Quantity_ColorRGBA_Test.cxx new file mode 100644 index 0000000000..78adb4bd0e --- /dev/null +++ b/src/FoundationClasses/TKernel/GTests/Quantity_ColorRGBA_Test.cxx @@ -0,0 +1,285 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include +#include + +#include +#include + +// Test fixture for Quantity_ColorRGBA tests +class Quantity_ColorRGBATest : public testing::Test +{ +protected: + void SetUp() override {} + + void TearDown() override {} + + // Helper to compare floating point values + bool IsNear(Standard_ShortReal theValue1, + Standard_ShortReal theValue2, + Standard_ShortReal theTolerance = 0.001f) const + { + return std::abs(theValue1 - theValue2) < theTolerance; + } +}; + +// Test basic construction +TEST_F(Quantity_ColorRGBATest, BasicConstruction) +{ + // Default constructor + Quantity_ColorRGBA aColor1; + EXPECT_TRUE(IsNear(1.0f, aColor1.GetRGB().Red())); // YELLOW = RGB(1,1,0) + EXPECT_TRUE(IsNear(1.0f, aColor1.GetRGB().Green())); // YELLOW = RGB(1,1,0) + EXPECT_TRUE(IsNear(0.0f, aColor1.GetRGB().Blue())); // YELLOW = RGB(1,1,0) + EXPECT_TRUE(IsNear(1.0f, aColor1.Alpha())); + + // Constructor with RGB + alpha + Quantity_ColorRGBA aColor2(Quantity_Color(0.5, 0.6, 0.7, Quantity_TOC_RGB), 0.8f); + EXPECT_TRUE(IsNear(0.5f, aColor2.GetRGB().Red())); + EXPECT_TRUE(IsNear(0.6f, aColor2.GetRGB().Green())); + EXPECT_TRUE(IsNear(0.7f, aColor2.GetRGB().Blue())); + EXPECT_TRUE(IsNear(0.8f, aColor2.Alpha())); + + // Constructor with RGBA floats + Quantity_ColorRGBA aColor3(0.3f, 0.4f, 0.5f, 0.6f); + EXPECT_TRUE(IsNear(0.3f, aColor3.GetRGB().Red())); + EXPECT_TRUE(IsNear(0.4f, aColor3.GetRGB().Green())); + EXPECT_TRUE(IsNear(0.5f, aColor3.GetRGB().Blue())); + EXPECT_TRUE(IsNear(0.6f, aColor3.Alpha())); +} + +// Test constexpr capabilities +TEST_F(Quantity_ColorRGBATest, ConstexprGetters) +{ + const Quantity_ColorRGBA aColor(0.2f, 0.4f, 0.6f, 0.8f); + + // These should work with constexpr + Standard_ShortReal aAlpha = aColor.Alpha(); + EXPECT_TRUE(IsNear(0.8f, aAlpha)); +} + +// Test SetValues +TEST_F(Quantity_ColorRGBATest, SetValues) +{ + Quantity_ColorRGBA aColor; + + aColor.SetValues(0.1f, 0.2f, 0.3f, 0.4f); + EXPECT_TRUE(IsNear(0.1f, aColor.GetRGB().Red())); + EXPECT_TRUE(IsNear(0.2f, aColor.GetRGB().Green())); + EXPECT_TRUE(IsNear(0.3f, aColor.GetRGB().Blue())); + EXPECT_TRUE(IsNear(0.4f, aColor.Alpha())); +} + +// Test hex color parsing (tests new HEX_BASE constant) +TEST_F(Quantity_ColorRGBATest, HexColorParsing_RGB) +{ + Quantity_ColorRGBA aColor; + + // Test standard RGB hex format: #RRGGBB + bool aResult = Quantity_ColorRGBA::ColorFromHex("#FF0000", aColor, true); + EXPECT_TRUE(aResult); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.01f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.01f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.01f)); + + // Test without # prefix + aResult = Quantity_ColorRGBA::ColorFromHex("00FF00", aColor, true); + EXPECT_TRUE(aResult); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Red(), 0.01f)); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Green(), 0.01f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.01f)); + + // Test blue + aResult = Quantity_ColorRGBA::ColorFromHex("#0000FF", aColor, true); + EXPECT_TRUE(aResult); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Red(), 0.01f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.01f)); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Blue(), 0.01f)); +} + +// Test RGBA hex format +TEST_F(Quantity_ColorRGBATest, HexColorParsing_RGBA) +{ + Quantity_ColorRGBA aColor; + + // Test RGBA hex format: #RRGGBBAA + bool aResult = Quantity_ColorRGBA::ColorFromHex("#FF000080", aColor, false); + EXPECT_TRUE(aResult); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.01f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.01f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.01f)); + EXPECT_TRUE(IsNear(0.5f, aColor.Alpha(), 0.02f)); // 0x80 / 0xFF ~= 0.5 +} + +// Test short hex format (tests HEX_BITS_PER_COMPONENT_SHORT constant) +TEST_F(Quantity_ColorRGBATest, HexColorParsing_ShortRGB) +{ + Quantity_ColorRGBA aColor; + + // Test short RGB hex format: #RGB (each component is 0-F) + bool aResult = Quantity_ColorRGBA::ColorFromHex("#F00", aColor, true); + EXPECT_TRUE(aResult); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.1f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.1f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.1f)); + + // Test short format for white + aResult = Quantity_ColorRGBA::ColorFromHex("#FFF", aColor, true); + EXPECT_TRUE(aResult); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.1f)); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Green(), 0.1f)); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Blue(), 0.1f)); +} + +// Test short RGBA hex format +TEST_F(Quantity_ColorRGBATest, HexColorParsing_ShortRGBA) +{ + Quantity_ColorRGBA aColor; + + // Test short RGBA hex format: #RGBA + bool aResult = Quantity_ColorRGBA::ColorFromHex("#F008", aColor, false); + EXPECT_TRUE(aResult); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.1f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Green(), 0.1f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.1f)); + EXPECT_TRUE(IsNear(0.5f, aColor.Alpha(), 0.1f)); // 0x8 / 0xF ~= 0.533 +} + +// Test invalid hex formats +TEST_F(Quantity_ColorRGBATest, HexColorParsing_Invalid) +{ + Quantity_ColorRGBA aColor; + + // Empty string + EXPECT_FALSE(Quantity_ColorRGBA::ColorFromHex("", aColor, true)); + + // Invalid length + EXPECT_FALSE(Quantity_ColorRGBA::ColorFromHex("#FFFF", aColor, true)); + + // Invalid characters + EXPECT_FALSE(Quantity_ColorRGBA::ColorFromHex("#GGGGGG", aColor, true)); + + // RGBA format when alpha is disabled + EXPECT_FALSE(Quantity_ColorRGBA::ColorFromHex("#FF0000FF", aColor, true)); +} + +// Test mixed case hex parsing +TEST_F(Quantity_ColorRGBATest, HexColorParsing_MixedCase) +{ + Quantity_ColorRGBA aColor; + + bool aResult = Quantity_ColorRGBA::ColorFromHex("#FfAa00", aColor, true); + EXPECT_TRUE(aResult); + EXPECT_TRUE(IsNear(1.0f, aColor.GetRGB().Red(), 0.01f)); + // AA = 170/255 = 0.667 sRGB, converts to ~0.402 linear RGB + EXPECT_TRUE(IsNear(0.402f, aColor.GetRGB().Green(), 0.01f)); + EXPECT_TRUE(IsNear(0.0f, aColor.GetRGB().Blue(), 0.01f)); +} + +// Test specific hex value from our constant migration (regression test) +TEST_F(Quantity_ColorRGBATest, HexColorParsing_SpecificValues) +{ + Quantity_ColorRGBA aColor; + + // Test a color that would use our HEX_BASE constant (16) + bool aResult = Quantity_ColorRGBA::ColorFromHex("#102030", aColor, true); + EXPECT_TRUE(aResult); + + // Hex values are sRGB that get converted to linear RGB + // 0x10 = 16/255 = 0.0627 sRGB -> 0.00518 linear RGB + // 0x20 = 32/255 = 0.1255 sRGB -> 0.01444 linear RGB + // 0x30 = 48/255 = 0.1882 sRGB -> 0.02956 linear RGB + EXPECT_TRUE(IsNear(0.00518f, aColor.GetRGB().Red(), 0.0001f)); + EXPECT_TRUE(IsNear(0.01444f, aColor.GetRGB().Green(), 0.0001f)); + EXPECT_TRUE(IsNear(0.02956f, aColor.GetRGB().Blue(), 0.0001f)); +} + +// Test equality comparison +TEST_F(Quantity_ColorRGBATest, EqualityComparison) +{ + Quantity_ColorRGBA aColor1(0.5f, 0.6f, 0.7f, 0.8f); + Quantity_ColorRGBA aColor2(0.5f, 0.6f, 0.7f, 0.8f); + Quantity_ColorRGBA aColor3(0.5f, 0.6f, 0.7f, 0.9f); // Different alpha + + EXPECT_TRUE(aColor1.IsEqual(aColor2)); + EXPECT_FALSE(aColor1.IsEqual(aColor3)); +} + +// Test GetRGB and ChangeRGB +TEST_F(Quantity_ColorRGBATest, RGBAccess) +{ + Quantity_ColorRGBA aColor(0.2f, 0.4f, 0.6f, 0.8f); + + const Quantity_Color& aRGB = aColor.GetRGB(); + EXPECT_TRUE(IsNear(0.2f, aRGB.Red())); + EXPECT_TRUE(IsNear(0.4f, aRGB.Green())); + EXPECT_TRUE(IsNear(0.6f, aRGB.Blue())); + + aColor.ChangeRGB().SetValues(0.3f, 0.5f, 0.7f, Quantity_TOC_RGB); + EXPECT_TRUE(IsNear(0.3f, aColor.GetRGB().Red())); + EXPECT_TRUE(IsNear(0.5f, aColor.GetRGB().Green())); + EXPECT_TRUE(IsNear(0.7f, aColor.GetRGB().Blue())); + EXPECT_TRUE(IsNear(0.8f, aColor.Alpha())); // Alpha unchanged +} + +// Test SetAlpha +TEST_F(Quantity_ColorRGBATest, SetAlpha) +{ + Quantity_ColorRGBA aColor(0.5f, 0.5f, 0.5f, 1.0f); + EXPECT_TRUE(IsNear(1.0f, aColor.Alpha())); + + aColor.SetAlpha(0.5f); + EXPECT_TRUE(IsNear(0.5f, aColor.Alpha())); + + // RGB should be unchanged + EXPECT_TRUE(IsNear(0.5f, aColor.GetRGB().Red())); + EXPECT_TRUE(IsNear(0.5f, aColor.GetRGB().Green())); + EXPECT_TRUE(IsNear(0.5f, aColor.GetRGB().Blue())); +} + +// Test edge cases +TEST_F(Quantity_ColorRGBATest, EdgeCases) +{ + // Fully transparent black + Quantity_ColorRGBA aTransparent(0.0f, 0.0f, 0.0f, 0.0f); + EXPECT_TRUE(IsNear(0.0f, aTransparent.Alpha())); + + // Fully opaque white + Quantity_ColorRGBA aOpaque(1.0f, 1.0f, 1.0f, 1.0f); + EXPECT_TRUE(IsNear(1.0f, aOpaque.Alpha())); + EXPECT_TRUE(IsNear(1.0f, aOpaque.GetRGB().Red())); +} + +// Test color component extraction in correct order (RGB_COMPONENT_LAST_INDEX) +TEST_F(Quantity_ColorRGBATest, ComponentOrder) +{ + Quantity_ColorRGBA aColor; + + // Parse a color where each component is distinct + bool aResult = Quantity_ColorRGBA::ColorFromHex("#123456", aColor, true); + EXPECT_TRUE(aResult); + + // Verify the order is correct: R=0x12, G=0x34, B=0x56 + // Hex values are sRGB that get converted to linear RGB + // 0x12 = 18/255 = 0.0706 sRGB -> 0.00605 linear RGB + // 0x34 = 52/255 = 0.2039 sRGB -> 0.03434 linear RGB + // 0x56 = 86/255 = 0.3373 sRGB -> 0.09306 linear RGB + float aExpectedR = 0.00605f; + float aExpectedG = 0.03434f; + float aExpectedB = 0.09306f; + + EXPECT_TRUE(IsNear(aExpectedR, aColor.GetRGB().Red(), 0.0001f)); + EXPECT_TRUE(IsNear(aExpectedG, aColor.GetRGB().Green(), 0.0001f)); + EXPECT_TRUE(IsNear(aExpectedB, aColor.GetRGB().Blue(), 0.0001f)); +} diff --git a/src/FoundationClasses/TKernel/GTests/Quantity_Color_Test.cxx b/src/FoundationClasses/TKernel/GTests/Quantity_Color_Test.cxx new file mode 100644 index 0000000000..2ddf33d7a5 --- /dev/null +++ b/src/FoundationClasses/TKernel/GTests/Quantity_Color_Test.cxx @@ -0,0 +1,293 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include +#include + +#include +#include + +// Test fixture for Quantity_Color tests +class Quantity_ColorTest : public testing::Test +{ +protected: + void SetUp() override {} + + void TearDown() override {} + + // Helper to compare floating point values + bool IsNear(Standard_Real theValue1, + Standard_Real theValue2, + Standard_Real theTolerance = 0.001) const + { + return std::abs(theValue1 - theValue2) < theTolerance; + } +}; + +// Test basic construction +TEST_F(Quantity_ColorTest, BasicConstruction) +{ + // Default constructor + Quantity_Color aColor1; + EXPECT_TRUE(IsNear(1.0, aColor1.Red())); // YELLOW = RGB(1,1,0) + EXPECT_TRUE(IsNear(1.0, aColor1.Green())); // YELLOW = RGB(1,1,0) + EXPECT_TRUE(IsNear(0.0, aColor1.Blue())); // YELLOW = RGB(1,1,0) + + // RGB constructor + Quantity_Color aColor2(0.5, 0.6, 0.7, Quantity_TOC_RGB); + EXPECT_TRUE(IsNear(0.5, aColor2.Red())); + EXPECT_TRUE(IsNear(0.6, aColor2.Green())); + EXPECT_TRUE(IsNear(0.7, aColor2.Blue())); + + // Named color constructor + Quantity_Color aColor3(Quantity_NOC_RED); + EXPECT_TRUE(IsNear(1.0, aColor3.Red())); + EXPECT_TRUE(IsNear(0.0, aColor3.Green())); + EXPECT_TRUE(IsNear(0.0, aColor3.Blue())); +} + +// Test constexpr getters (compile-time evaluation capability) +TEST_F(Quantity_ColorTest, ConstexprGetters) +{ + const Quantity_Color aColor(0.3, 0.5, 0.7, Quantity_TOC_RGB); + + // These should work at compile-time with constexpr + Standard_Real aR = aColor.Red(); + Standard_Real aG = aColor.Green(); + Standard_Real aB = aColor.Blue(); + + EXPECT_TRUE(IsNear(0.3, aR)); + EXPECT_TRUE(IsNear(0.5, aG)); + EXPECT_TRUE(IsNear(0.7, aB)); +} + +// Test equality comparison (noexcept guarantee) +TEST_F(Quantity_ColorTest, EqualityComparison) +{ + Quantity_Color aColor1(0.5, 0.6, 0.7, Quantity_TOC_RGB); + Quantity_Color aColor2(0.5, 0.6, 0.7, Quantity_TOC_RGB); + Quantity_Color aColor3(0.5, 0.6, 0.8, Quantity_TOC_RGB); + + EXPECT_TRUE(aColor1.IsEqual(aColor2)); + EXPECT_TRUE(aColor1 == aColor2); + EXPECT_FALSE(aColor1.IsDifferent(aColor2)); + EXPECT_FALSE(aColor1 != aColor2); + + EXPECT_FALSE(aColor1.IsEqual(aColor3)); + EXPECT_FALSE(aColor1 == aColor3); + EXPECT_TRUE(aColor1.IsDifferent(aColor3)); + EXPECT_TRUE(aColor1 != aColor3); +} + +// Test distance calculation (noexcept guarantee) +TEST_F(Quantity_ColorTest, DistanceCalculation) +{ + Quantity_Color aColor1(0.0, 0.0, 0.0, Quantity_TOC_RGB); + Quantity_Color aColor2(0.3, 0.4, 0.0, Quantity_TOC_RGB); + + // Distance should be sqrt(0.3^2 + 0.4^2) = sqrt(0.09 + 0.16) = sqrt(0.25) = 0.5 + Standard_Real aDist = aColor1.Distance(aColor2); + EXPECT_TRUE(IsNear(0.5, aDist)); + + Standard_Real aSquareDist = aColor1.SquareDistance(aColor2); + EXPECT_TRUE(IsNear(0.25, aSquareDist)); +} + +// Test sRGB to HLS conversion +TEST_F(Quantity_ColorTest, RGB_to_HLS_Conversion) +{ + // Pure red in sRGB + Quantity_Color aRed(Quantity_NOC_RED); + NCollection_Vec3 aHLS = Quantity_Color::Convert_sRGB_To_HLS(aRed.Rgb()); + + EXPECT_TRUE(IsNear(0.0, aHLS[0], 1.0)); // Hue for red should be ~0 + EXPECT_TRUE(IsNear(1.0, aHLS[1])); // Lightness should be 1 (max value) + EXPECT_TRUE(IsNear(1.0, aHLS[2])); // Saturation should be 1 (fully saturated) + + // Gray (no saturation) + Quantity_Color aGray(0.5, 0.5, 0.5, Quantity_TOC_RGB); + NCollection_Vec3 aHLS_Gray = Quantity_Color::Convert_sRGB_To_HLS(aGray.Rgb()); + + EXPECT_TRUE(IsNear(0.5, aHLS_Gray[1])); // Lightness + EXPECT_TRUE(IsNear(0.0, aHLS_Gray[2])); // Saturation should be 0 for gray +} + +// Test Linear RGB to CIE Lab conversion (uses new constexpr constants) +TEST_F(Quantity_ColorTest, LinearRGB_to_Lab_Conversion) +{ + // White should convert to L=100, a=0, b=0 in Lab + Quantity_Color aWhite(1.0, 1.0, 1.0, Quantity_TOC_RGB); + NCollection_Vec3 aLab = Quantity_Color::Convert_LinearRGB_To_Lab(aWhite.Rgb()); + + EXPECT_TRUE(IsNear(100.0, aLab[0], 1.0)); // L should be near 100 + EXPECT_TRUE(IsNear(0.0, aLab[1], 5.0)); // a should be near 0 + EXPECT_TRUE(IsNear(0.0, aLab[2], 5.0)); // b should be near 0 + + // Black should convert to L=0 + Quantity_Color aBlack(0.0, 0.0, 0.0, Quantity_TOC_RGB); + NCollection_Vec3 aLabBlack = Quantity_Color::Convert_LinearRGB_To_Lab(aBlack.Rgb()); + + EXPECT_TRUE(IsNear(0.0, aLabBlack[0], 1.0)); // L should be 0 +} + +// Test Lab to Lch conversion +TEST_F(Quantity_ColorTest, Lab_to_Lch_Conversion) +{ + // Test with known Lab values + NCollection_Vec3 aLab(50.0f, 25.0f, 25.0f); + NCollection_Vec3 aLch = Quantity_Color::Convert_Lab_To_Lch(aLab); + + EXPECT_TRUE(IsNear(50.0, aLch[0])); // L should be preserved + + // C (chroma) should be sqrt(25^2 + 25^2) = sqrt(1250) ~= 35.36 + EXPECT_TRUE(IsNear(35.36, aLch[1], 0.1)); + + // H (hue) should be atan2(25, 25) * 180/pi = 45 degrees + EXPECT_TRUE(IsNear(45.0, aLch[2], 1.0)); +} + +// Test Lch to Lab conversion (round-trip) +TEST_F(Quantity_ColorTest, Lch_to_Lab_RoundTrip) +{ + NCollection_Vec3 aLab1(50.0f, 25.0f, 25.0f); + NCollection_Vec3 aLch = Quantity_Color::Convert_Lab_To_Lch(aLab1); + NCollection_Vec3 aLab2 = Quantity_Color::Convert_Lch_To_Lab(aLch); + + EXPECT_TRUE(IsNear(aLab1[0], aLab2[0], 0.01)); + EXPECT_TRUE(IsNear(aLab1[1], aLab2[1], 0.01)); + EXPECT_TRUE(IsNear(aLab1[2], aLab2[2], 0.01)); +} + +// Test Lab to RGB conversion (round-trip validation) +TEST_F(Quantity_ColorTest, Lab_to_RGB_RoundTrip) +{ + Quantity_Color aOriginal(0.5, 0.6, 0.7, Quantity_TOC_RGB); + NCollection_Vec3 aLab = Quantity_Color::Convert_LinearRGB_To_Lab(aOriginal.Rgb()); + NCollection_Vec3 aRGB = Quantity_Color::Convert_Lab_To_LinearRGB(aLab); + + EXPECT_TRUE(IsNear(aOriginal.Red(), aRGB[0], 0.01)); + EXPECT_TRUE(IsNear(aOriginal.Green(), aRGB[1], 0.01)); + EXPECT_TRUE(IsNear(aOriginal.Blue(), aRGB[2], 0.01)); +} + +// Test DeltaE2000 color difference (uses Epsilon() function - regression test for bug fix) +TEST_F(Quantity_ColorTest, DeltaE2000_Calculation) +{ + // Same color should have DeltaE = 0 + Quantity_Color aColor1(0.5, 0.6, 0.7, Quantity_TOC_RGB); + Quantity_Color aColor2(0.5, 0.6, 0.7, Quantity_TOC_RGB); + + Standard_Real aDeltaE = aColor1.DeltaE2000(aColor2); + EXPECT_TRUE(IsNear(0.0, aDeltaE, 0.01)); + + // Different colors should have non-zero DeltaE + Quantity_Color aColor3(0.3, 0.4, 0.5, Quantity_TOC_RGB); + Standard_Real aDeltaE2 = aColor1.DeltaE2000(aColor3); + EXPECT_GT(aDeltaE2, 0.0); +} + +// Test named color conversion +TEST_F(Quantity_ColorTest, NamedColors) +{ + // Test a few standard colors + Quantity_Color aRed(Quantity_NOC_RED); + EXPECT_TRUE(IsNear(1.0, aRed.Red())); + EXPECT_TRUE(IsNear(0.0, aRed.Green())); + EXPECT_TRUE(IsNear(0.0, aRed.Blue())); + + Quantity_Color aGreen(Quantity_NOC_GREEN); + EXPECT_TRUE(IsNear(0.0, aGreen.Red())); + EXPECT_GT(aGreen.Green(), 0.5); // Green should be significant + EXPECT_TRUE(IsNear(0.0, aGreen.Blue())); + + Quantity_Color aBlue(Quantity_NOC_BLUE); + EXPECT_TRUE(IsNear(0.0, aBlue.Red())); + EXPECT_TRUE(IsNear(0.0, aBlue.Green())); + EXPECT_TRUE(IsNear(1.0, aBlue.Blue())); +} + +// Test SetValues and modification +TEST_F(Quantity_ColorTest, SetValues) +{ + Quantity_Color aColor; + + aColor.SetValues(0.2, 0.4, 0.6, Quantity_TOC_RGB); + EXPECT_TRUE(IsNear(0.2, aColor.Red())); + EXPECT_TRUE(IsNear(0.4, aColor.Green())); + EXPECT_TRUE(IsNear(0.6, aColor.Blue())); + + aColor.SetValues(Quantity_NOC_YELLOW); + EXPECT_TRUE(IsNear(1.0, aColor.Red())); + EXPECT_TRUE(IsNear(1.0, aColor.Green())); + EXPECT_TRUE(IsNear(0.0, aColor.Blue())); +} + +// Test HLS values extraction +TEST_F(Quantity_ColorTest, HLS_Extraction) +{ + Quantity_Color aRed(Quantity_NOC_RED); + + // For pure red, hue should be ~0, saturation should be 1, lightness should be 1 + Standard_Real aHue = aRed.Hue(); + Standard_Real aLight = aRed.Light(); + Standard_Real aSat = aRed.Saturation(); + + EXPECT_TRUE(IsNear(0.0, aHue, 5.0) || IsNear(360.0, aHue, 5.0)); // Hue wraps around + EXPECT_TRUE(IsNear(1.0, aLight, 0.01)); + EXPECT_TRUE(IsNear(1.0, aSat, 0.01)); +} + +// Test thread-safety of Epsilon getter/setter +TEST_F(Quantity_ColorTest, EpsilonThreadSafety) +{ + Standard_Real aOriginalEpsilon = Quantity_Color::Epsilon(); + + // Set new epsilon + Quantity_Color::SetEpsilon(0.0002); + EXPECT_TRUE(IsNear(0.0002, Quantity_Color::Epsilon())); + + // Restore original + Quantity_Color::SetEpsilon(aOriginalEpsilon); + EXPECT_TRUE(IsNear(aOriginalEpsilon, Quantity_Color::Epsilon())); +} + +// Test color name string conversion +TEST_F(Quantity_ColorTest, ColorNameString) +{ + Standard_CString aRedName = Quantity_Color::StringName(Quantity_NOC_RED); + EXPECT_STREQ("RED", aRedName); + + Standard_CString aBlueName = Quantity_Color::StringName(Quantity_NOC_BLUE); + EXPECT_STREQ("BLUE", aBlueName); +} + +// Test edge cases and boundary conditions +TEST_F(Quantity_ColorTest, EdgeCases) +{ + // Test with zero values + Quantity_Color aBlack(0.0, 0.0, 0.0, Quantity_TOC_RGB); + EXPECT_TRUE(IsNear(0.0, aBlack.Red())); + EXPECT_TRUE(IsNear(0.0, aBlack.Green())); + EXPECT_TRUE(IsNear(0.0, aBlack.Blue())); + + // Test with max values + Quantity_Color aWhite(1.0, 1.0, 1.0, Quantity_TOC_RGB); + EXPECT_TRUE(IsNear(1.0, aWhite.Red())); + EXPECT_TRUE(IsNear(1.0, aWhite.Green())); + EXPECT_TRUE(IsNear(1.0, aWhite.Blue())); + + // Test equality with epsilon tolerance + Quantity_Color aColor1(0.5, 0.5, 0.5, Quantity_TOC_RGB); + Quantity_Color aColor2(0.50001, 0.50001, 0.50001, Quantity_TOC_RGB); + EXPECT_TRUE(aColor1.IsEqual(aColor2)); // Should be equal within epsilon +} diff --git a/src/FoundationClasses/TKernel/GTests/Quantity_Date_Test.cxx b/src/FoundationClasses/TKernel/GTests/Quantity_Date_Test.cxx new file mode 100644 index 0000000000..932d312df8 --- /dev/null +++ b/src/FoundationClasses/TKernel/GTests/Quantity_Date_Test.cxx @@ -0,0 +1,409 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include +#include + +#include + +// Test fixture for Quantity_Date tests +class Quantity_DateTest : public testing::Test +{ +protected: + void SetUp() override {} + + void TearDown() override {} +}; + +// Test basic construction and default initialization +TEST_F(Quantity_DateTest, BasicConstruction) +{ + // Default constructor creates January 1, 1979, 00:00:00 + Quantity_Date aDate1; + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + aDate1.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(1, mm); // January + EXPECT_EQ(1, dd); // 1st + EXPECT_EQ(1979, yy); // 1979 + EXPECT_EQ(0, hh); // 00 hours + EXPECT_EQ(0, mn); // 00 minutes + EXPECT_EQ(0, ss); // 00 seconds + + // Parameterized constructor + Quantity_Date aDate2(6, 15, 2025, 14, 30, 45, 123, 456); + aDate2.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(6, mm); + EXPECT_EQ(15, dd); + EXPECT_EQ(2025, yy); + EXPECT_EQ(14, hh); + EXPECT_EQ(30, mn); + EXPECT_EQ(45, ss); + EXPECT_EQ(123, mis); + EXPECT_EQ(456, mics); +} + +// Test constexpr comparison operators +TEST_F(Quantity_DateTest, ConstexprComparisons) +{ + Quantity_Date aDate1(1, 1, 2020, 0, 0, 0, 0, 0); + Quantity_Date aDate2(1, 1, 2020, 0, 0, 0, 0, 0); + Quantity_Date aDate3(1, 2, 2020, 0, 0, 0, 0, 0); + + // Test IsEqual (constexpr) + EXPECT_TRUE(aDate1.IsEqual(aDate2)); + EXPECT_FALSE(aDate1.IsEqual(aDate3)); + + // Test IsEarlier (constexpr) + EXPECT_TRUE(aDate1.IsEarlier(aDate3)); + EXPECT_FALSE(aDate3.IsEarlier(aDate1)); + EXPECT_FALSE(aDate1.IsEarlier(aDate2)); + + // Test IsLater (constexpr) + EXPECT_TRUE(aDate3.IsLater(aDate1)); + EXPECT_FALSE(aDate1.IsLater(aDate3)); + EXPECT_FALSE(aDate1.IsLater(aDate2)); +} + +// Test leap year detection (constexpr) +TEST_F(Quantity_DateTest, LeapYearDetection) +{ + // Leap years + EXPECT_TRUE(Quantity_Date::IsLeap(2000)); // Divisible by 400 + EXPECT_TRUE(Quantity_Date::IsLeap(2004)); // Divisible by 4, not by 100 + EXPECT_TRUE(Quantity_Date::IsLeap(2020)); + + // Non-leap years + EXPECT_FALSE(Quantity_Date::IsLeap(1900)); // Divisible by 100 but not 400 + EXPECT_FALSE(Quantity_Date::IsLeap(2001)); // Not divisible by 4 + EXPECT_FALSE(Quantity_Date::IsLeap(2100)); // Divisible by 100 but not 400 +} + +// Test validation with leap years (thread-safety regression test) +TEST_F(Quantity_DateTest, ValidationWithLeapYear) +{ + // Valid date in leap year (February 29) + EXPECT_TRUE(Quantity_Date::IsValid(2, 29, 2020, 0, 0, 0, 0, 0)); + + // Invalid date in non-leap year (February 29) + EXPECT_FALSE(Quantity_Date::IsValid(2, 29, 2019, 0, 0, 0, 0, 0)); + + // Valid February 28 in any year + EXPECT_TRUE(Quantity_Date::IsValid(2, 28, 2019, 0, 0, 0, 0, 0)); + EXPECT_TRUE(Quantity_Date::IsValid(2, 28, 2020, 0, 0, 0, 0, 0)); +} + +// Test month boundary validation (uses constexpr getDaysInMonth) +TEST_F(Quantity_DateTest, MonthBoundaries) +{ + // January has 31 days + EXPECT_TRUE(Quantity_Date::IsValid(1, 31, 2020, 0, 0, 0, 0, 0)); + EXPECT_FALSE(Quantity_Date::IsValid(1, 32, 2020, 0, 0, 0, 0, 0)); + + // April has 30 days + EXPECT_TRUE(Quantity_Date::IsValid(4, 30, 2020, 0, 0, 0, 0, 0)); + EXPECT_FALSE(Quantity_Date::IsValid(4, 31, 2020, 0, 0, 0, 0, 0)); + + // December has 31 days + EXPECT_TRUE(Quantity_Date::IsValid(12, 31, 2020, 0, 0, 0, 0, 0)); + EXPECT_FALSE(Quantity_Date::IsValid(12, 32, 2020, 0, 0, 0, 0, 0)); +} + +// Test time validation (uses time constants) +TEST_F(Quantity_DateTest, TimeValidation) +{ + // Valid times + EXPECT_TRUE(Quantity_Date::IsValid(1, 1, 2020, 23, 59, 59, 999, 999)); + EXPECT_TRUE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, 0, 0)); + + // Invalid hours + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 24, 0, 0, 0, 0)); + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, -1, 0, 0, 0, 0)); + + // Invalid minutes + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 60, 0, 0, 0)); + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, -1, 0, 0, 0)); + + // Invalid seconds + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 60, 0, 0)); + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, -1, 0, 0)); + + // Invalid milliseconds + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, 1000, 0)); + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, -1, 0)); + + // Invalid microseconds + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, 0, 1000)); + EXPECT_FALSE(Quantity_Date::IsValid(1, 1, 2020, 0, 0, 0, 0, -1)); +} + +// Test SetValues and Values round-trip (tests SECONDS_PER_* constants) +TEST_F(Quantity_DateTest, SetValuesRoundTrip) +{ + Quantity_Date aDate; + aDate.SetValues(3, 15, 2021, 10, 25, 30, 500, 750); + + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + aDate.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(3, mm); + EXPECT_EQ(15, dd); + EXPECT_EQ(2021, yy); + EXPECT_EQ(10, hh); + EXPECT_EQ(25, mn); + EXPECT_EQ(30, ss); + EXPECT_EQ(500, mis); + EXPECT_EQ(750, mics); +} + +// Test individual component getters +TEST_F(Quantity_DateTest, IndividualGetters) +{ + // Test with a simple date first: January 2, 1979 + Quantity_Date aDate1(1, 2, 1979, 0, 0, 0, 0, 0); + EXPECT_EQ(1, aDate1.Month()); + EXPECT_EQ(2, aDate1.Day()); + EXPECT_EQ(1979, aDate1.Year()); + + // Test with July 20, 2024 at midnight + Quantity_Date aDate2(7, 20, 2024, 0, 0, 0, 0, 0); + EXPECT_EQ(7, aDate2.Month()); + EXPECT_EQ(20, aDate2.Day()); + EXPECT_EQ(2024, aDate2.Year()); + + // Test with July 20, 2024 with time components + Quantity_Date aDate3(7, 20, 2024, 15, 45, 30, 123, 456); + EXPECT_EQ(7, aDate3.Month()); + EXPECT_EQ(20, aDate3.Day()); + EXPECT_EQ(2024, aDate3.Year()); + EXPECT_EQ(15, aDate3.Hour()); + EXPECT_EQ(45, aDate3.Minute()); + EXPECT_EQ(30, aDate3.Second()); + EXPECT_EQ(123, aDate3.MilliSecond()); + EXPECT_EQ(456, aDate3.MicroSecond()); +} + +// Test date difference calculation (uses USECS_PER_SEC constant) +TEST_F(Quantity_DateTest, DateDifference) +{ + Quantity_Date aDate1(1, 1, 2020, 0, 0, 0, 0, 0); + Quantity_Date aDate2(1, 1, 2020, 1, 0, 0, 0, 0); // 1 hour later + + Quantity_Period aPeriod = aDate2.Difference(aDate1); + + Standard_Integer ss, mics; + aPeriod.Values(ss, mics); + + EXPECT_EQ(3600, ss); // 1 hour = 3600 seconds (SECONDS_PER_HOUR) + EXPECT_EQ(0, mics); +} + +// Test adding period to date (uses USECS_PER_SEC constant) +TEST_F(Quantity_DateTest, AddPeriod) +{ + Quantity_Date aDate(1, 1, 2020, 0, 0, 0, 0, 0); + Quantity_Period aPeriod(1, 0, 0, 0, 0, 0); // 1 day + + Quantity_Date aNewDate = aDate.Add(aPeriod); + + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + aNewDate.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(1, mm); + EXPECT_EQ(2, dd); // Next day + EXPECT_EQ(2020, yy); +} + +// Test subtracting period from date +TEST_F(Quantity_DateTest, SubtractPeriod) +{ + Quantity_Date aDate(1, 2, 2020, 0, 0, 0, 0, 0); // January 2 + Quantity_Period aPeriod(1, 0, 0, 0, 0, 0); // 1 day + + Quantity_Date aNewDate = aDate.Subtract(aPeriod); + + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + aNewDate.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(1, mm); + EXPECT_EQ(1, dd); // Previous day + EXPECT_EQ(2020, yy); +} + +// Test year boundary crossing +TEST_F(Quantity_DateTest, YearBoundary) +{ + Quantity_Date aDate(12, 31, 2020, 23, 59, 59, 0, 0); + Quantity_Period aPeriod(0, 0, 0, 1, 0, 0); // 1 second + + Quantity_Date aNewDate = aDate.Add(aPeriod); + + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + aNewDate.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(1, mm); // January + EXPECT_EQ(1, dd); // 1st + EXPECT_EQ(2021, yy); // Next year + EXPECT_EQ(0, hh); + EXPECT_EQ(0, mn); + EXPECT_EQ(0, ss); +} + +// Test leap year boundary (Feb 28/29) +TEST_F(Quantity_DateTest, LeapYearBoundary) +{ + // Leap year: Feb 28 + 1 day = Feb 29 + Quantity_Date aDate1(2, 28, 2020, 0, 0, 0, 0, 0); + Quantity_Period aPeriod1(1, 0, 0, 0, 0, 0); + Quantity_Date aNewDate1 = aDate1.Add(aPeriod1); + + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + aNewDate1.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(2, mm); + EXPECT_EQ(29, dd); // Feb 29 exists in 2020 + EXPECT_EQ(2020, yy); + + // Non-leap year: Feb 28 + 1 day = Mar 1 + Quantity_Date aDate2(2, 28, 2021, 0, 0, 0, 0, 0); + Quantity_Period aPeriod2(1, 0, 0, 0, 0, 0); + Quantity_Date aNewDate2 = aDate2.Add(aPeriod2); + + aNewDate2.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(3, mm); // March + EXPECT_EQ(1, dd); // 1st + EXPECT_EQ(2021, yy); +} + +// Test microsecond overflow (uses USECS_PER_SEC constant) +TEST_F(Quantity_DateTest, MicrosecondOverflow) +{ + Quantity_Date aDate(1, 1, 2020, 0, 0, 0, 999, 999); + Quantity_Period aPeriod(0, 0, 0, 0, 0, 1); // 1 microsecond + + Quantity_Date aNewDate = aDate.Add(aPeriod); + + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + aNewDate.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(1, mm); + EXPECT_EQ(1, dd); + EXPECT_EQ(2020, yy); + EXPECT_EQ(0, hh); + EXPECT_EQ(0, mn); + EXPECT_EQ(1, ss); // Should overflow to next second + EXPECT_EQ(0, mis); + EXPECT_EQ(0, mics); +} + +// Test specific date calculations (regression test for time constants) +TEST_F(Quantity_DateTest, SpecificDateCalculations) +{ + // Test that 24 hours = 1 day (SECONDS_PER_DAY = 86400) + Quantity_Date aDate1(1, 1, 2020, 0, 0, 0, 0, 0); + Quantity_Period aPeriod24h(0, 24, 0, 0, 0, 0); // 24 hours + + Quantity_Date aDate2 = aDate1.Add(aPeriod24h); + + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + aDate2.Values(mm, dd, yy, hh, mn, ss, mis, mics); + + EXPECT_EQ(1, mm); + EXPECT_EQ(2, dd); // Next day + EXPECT_EQ(2020, yy); + EXPECT_EQ(0, hh); +} + +// Test minimum date (January 1, 1979) +TEST_F(Quantity_DateTest, MinimumDate) +{ + EXPECT_TRUE(Quantity_Date::IsValid(1, 1, 1979, 0, 0, 0, 0, 0)); + EXPECT_FALSE(Quantity_Date::IsValid(12, 31, 1978, 23, 59, 59, 999, 999)); +} + +// Test thread-safety of getDaysInMonth (implicit test) +TEST_F(Quantity_DateTest, MultipleLeapYearChecks) +{ + // Repeatedly check different months and years + // This would catch thread-safety issues with mutable month_table + for (int year = 2000; year <= 2024; ++year) + { + for (int month = 1; month <= 12; ++month) + { + int maxDay; + if (month == 2) + { + maxDay = Quantity_Date::IsLeap(year) ? 29 : 28; + } + else if (month == 4 || month == 6 || month == 9 || month == 11) + { + maxDay = 30; + } + else + { + maxDay = 31; + } + + EXPECT_TRUE(Quantity_Date::IsValid(month, maxDay, year, 0, 0, 0, 0, 0)); + EXPECT_FALSE(Quantity_Date::IsValid(month, maxDay + 1, year, 0, 0, 0, 0, 0)); + } + } +} + +// Test date difference with epoch (Jan 1, 1979 00:00) - special case +TEST_F(Quantity_DateTest, DifferenceFromEpoch) +{ + Quantity_Date aEpoch; // Defaults to Jan 1, 1979 00:00 + Quantity_Date aDate(1, 2, 1979, 0, 0, 0, 0, 0); // Jan 2, 1979 + + Quantity_Period aPeriod = aEpoch.Difference(aDate); + + Standard_Integer ss, mics; + aPeriod.Values(ss, mics); + + EXPECT_EQ(86400, ss); // 1 day = 86400 seconds + EXPECT_EQ(0, mics); +} + +// Test date difference with microsecond underflow +TEST_F(Quantity_DateTest, DifferenceWithMicrosecondUnderflow) +{ + Quantity_Date aDate1(1, 1, 2020, 0, 0, 1, 200, 0); // 1.2 seconds + Quantity_Date aDate2(1, 1, 2020, 0, 0, 0, 500, 0); // 0.5 seconds + + Quantity_Period aPeriod = aDate1.Difference(aDate2); + + Standard_Integer ss, mics; + aPeriod.Values(ss, mics); + + EXPECT_EQ(0, ss); + EXPECT_EQ(700000, mics); // 1.2 - 0.5 = 0.7 seconds = 700000 microseconds +} + +// Test date difference with reversed dates (absolute value) +TEST_F(Quantity_DateTest, DifferenceReversed) +{ + Quantity_Date aDate1(1, 1, 2020, 1, 0, 0, 0, 0); // 1 hour + Quantity_Date aDate2(1, 1, 2020, 3, 0, 0, 0, 0); // 3 hours + + // Difference should be absolute value + Quantity_Period aPeriod = aDate1.Difference(aDate2); + + Standard_Integer ss, mics; + aPeriod.Values(ss, mics); + + EXPECT_EQ(7200, ss); // 2 hours = 7200 seconds + EXPECT_EQ(0, mics); +} diff --git a/src/FoundationClasses/TKernel/GTests/Quantity_Period_Test.cxx b/src/FoundationClasses/TKernel/GTests/Quantity_Period_Test.cxx new file mode 100644 index 0000000000..d68a2d3db9 --- /dev/null +++ b/src/FoundationClasses/TKernel/GTests/Quantity_Period_Test.cxx @@ -0,0 +1,393 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include + +// Test fixture for Quantity_Period tests +class Quantity_PeriodTest : public testing::Test +{ +protected: + void SetUp() override {} + + void TearDown() override {} +}; + +// Test basic construction +TEST_F(Quantity_PeriodTest, BasicConstruction) +{ + // Constructor with days, hours, minutes, seconds, milliseconds, microseconds + Quantity_Period aPeriod1(1, 2, 3, 4, 5, 6); + + Standard_Integer dd, hh, mn, ss, mis, mics; + aPeriod1.Values(dd, hh, mn, ss, mis, mics); + + EXPECT_EQ(1, dd); + EXPECT_EQ(2, hh); + EXPECT_EQ(3, mn); + EXPECT_EQ(4, ss); + EXPECT_EQ(5, mis); + EXPECT_EQ(6, mics); + + // Constructor with seconds and microseconds + Quantity_Period aPeriod2(3600, 500000); // 1 hour, 500000 microseconds + + Standard_Integer ss2, mics2; + aPeriod2.Values(ss2, mics2); + + EXPECT_EQ(3600, ss2); // SECONDS_PER_HOUR + EXPECT_EQ(500000, mics2); +} + +// Test constexpr comparison operators +TEST_F(Quantity_PeriodTest, ConstexprComparisons) +{ + Quantity_Period aPeriod1(0, 0, 0, 100, 0, 0); + Quantity_Period aPeriod2(0, 0, 0, 100, 0, 0); + Quantity_Period aPeriod3(0, 0, 0, 200, 0, 0); + + // Test IsEqual (constexpr) + EXPECT_TRUE(aPeriod1.IsEqual(aPeriod2)); + EXPECT_FALSE(aPeriod1.IsEqual(aPeriod3)); + + // Test IsShorter (constexpr) + EXPECT_TRUE(aPeriod1.IsShorter(aPeriod3)); + EXPECT_FALSE(aPeriod3.IsShorter(aPeriod1)); + EXPECT_FALSE(aPeriod1.IsShorter(aPeriod2)); + + // Test IsLonger (constexpr) + EXPECT_TRUE(aPeriod3.IsLonger(aPeriod1)); + EXPECT_FALSE(aPeriod1.IsLonger(aPeriod3)); + EXPECT_FALSE(aPeriod1.IsLonger(aPeriod2)); +} + +// Test validation +TEST_F(Quantity_PeriodTest, Validation) +{ + // Valid periods + EXPECT_TRUE(Quantity_Period::IsValid(1, 2, 3, 4, 5, 6)); + EXPECT_TRUE(Quantity_Period::IsValid(0, 0, 0, 0, 0, 0)); + + // Invalid periods (negative values) + EXPECT_FALSE(Quantity_Period::IsValid(-1, 0, 0, 0, 0, 0)); + EXPECT_FALSE(Quantity_Period::IsValid(0, -1, 0, 0, 0, 0)); + EXPECT_FALSE(Quantity_Period::IsValid(0, 0, -1, 0, 0, 0)); + EXPECT_FALSE(Quantity_Period::IsValid(0, 0, 0, -1, 0, 0)); + EXPECT_FALSE(Quantity_Period::IsValid(0, 0, 0, 0, -1, 0)); + EXPECT_FALSE(Quantity_Period::IsValid(0, 0, 0, 0, 0, -1)); + + // Validation with seconds and microseconds only + EXPECT_TRUE(Quantity_Period::IsValid(100, 500)); + EXPECT_FALSE(Quantity_Period::IsValid(-1, 500)); + EXPECT_FALSE(Quantity_Period::IsValid(100, -1)); +} + +// Test SetValues and Values round-trip (tests time constants) +TEST_F(Quantity_PeriodTest, SetValuesRoundTrip) +{ + Quantity_Period aPeriod(0, 0); // Initialize with zero period + aPeriod.SetValues(2, 3, 4, 5, 6, 7); + + Standard_Integer dd, hh, mn, ss, mis, mics; + aPeriod.Values(dd, hh, mn, ss, mis, mics); + + EXPECT_EQ(2, dd); + EXPECT_EQ(3, hh); + EXPECT_EQ(4, mn); + EXPECT_EQ(5, ss); + EXPECT_EQ(6, mis); + EXPECT_EQ(7, mics); +} + +// Test conversion between formats (uses SECONDS_PER_DAY, etc.) +TEST_F(Quantity_PeriodTest, FormatConversion) +{ + // 1 day should equal 86400 seconds (SECONDS_PER_DAY) + Quantity_Period aPeriod1(1, 0, 0, 0, 0, 0); + + Standard_Integer ss, mics; + aPeriod1.Values(ss, mics); + + EXPECT_EQ(86400, ss); // SECONDS_PER_DAY = 24 * 3600 + + // 1 hour should equal 3600 seconds (SECONDS_PER_HOUR) + Quantity_Period aPeriod2(0, 1, 0, 0, 0, 0); + aPeriod2.Values(ss, mics); + + EXPECT_EQ(3600, ss); // SECONDS_PER_HOUR + + // 1 minute should equal 60 seconds (SECONDS_PER_MINUTE) + Quantity_Period aPeriod3(0, 0, 1, 0, 0, 0); + aPeriod3.Values(ss, mics); + + EXPECT_EQ(60, ss); // SECONDS_PER_MINUTE +} + +// Test millisecond to microsecond conversion (uses USECS_PER_MSEC) +TEST_F(Quantity_PeriodTest, MillisecondConversion) +{ + // 1 millisecond = 1000 microseconds (USECS_PER_MSEC) + Quantity_Period aPeriod(0, 0, 0, 0, 1, 0); + + Standard_Integer ss, mics; + aPeriod.Values(ss, mics); + + EXPECT_EQ(0, ss); + EXPECT_EQ(1000, mics); // USECS_PER_MSEC + + // Mixed milliseconds and microseconds + Quantity_Period aPeriod2(0, 0, 0, 0, 5, 250); + aPeriod2.Values(ss, mics); + + EXPECT_EQ(0, ss); + EXPECT_EQ(5250, mics); // 5 * 1000 + 250 +} + +// Test microsecond overflow (uses USECS_PER_SEC constant) +TEST_F(Quantity_PeriodTest, MicrosecondOverflow) +{ + // Create period with > 1,000,000 microseconds + Quantity_Period aPeriod(0, 0); // Initialize with zero period + aPeriod.SetValues(0, 1500000); // 1.5 seconds in microseconds + + Standard_Integer ss, mics; + aPeriod.Values(ss, mics); + + EXPECT_EQ(1, ss); // 1 second + EXPECT_EQ(500000, mics); // 0.5 second = 500000 microseconds +} + +// Test adding periods (uses USECS_PER_SEC constant) +TEST_F(Quantity_PeriodTest, AddPeriods) +{ + Quantity_Period aPeriod1(0, 1, 0, 0, 0, 0); // 1 hour + Quantity_Period aPeriod2(0, 2, 0, 0, 0, 0); // 2 hours + + Quantity_Period aResult = aPeriod1.Add(aPeriod2); + + Standard_Integer ss, mics; + aResult.Values(ss, mics); + + EXPECT_EQ(10800, ss); // 3 hours = 3 * 3600 = 10800 seconds + EXPECT_EQ(0, mics); +} + +// Test adding with microsecond overflow +TEST_F(Quantity_PeriodTest, AddWithMicrosecondOverflow) +{ + Quantity_Period aPeriod1(0, 0, 0, 0, 0, 600000); // 0.6 seconds + Quantity_Period aPeriod2(0, 0, 0, 0, 0, 600000); // 0.6 seconds + + Quantity_Period aResult = aPeriod1.Add(aPeriod2); + + Standard_Integer ss, mics; + aResult.Values(ss, mics); + + EXPECT_EQ(1, ss); // Should overflow to 1 second + EXPECT_EQ(200000, mics); // Remaining 0.2 seconds +} + +// Test subtracting periods (uses USECS_PER_SEC constant) +TEST_F(Quantity_PeriodTest, SubtractPeriods) +{ + Quantity_Period aPeriod1(0, 3, 0, 0, 0, 0); // 3 hours + Quantity_Period aPeriod2(0, 1, 0, 0, 0, 0); // 1 hour + + Quantity_Period aResult = aPeriod1.Subtract(aPeriod2); + + Standard_Integer ss, mics; + aResult.Values(ss, mics); + + EXPECT_EQ(7200, ss); // 2 hours = 2 * 3600 + EXPECT_EQ(0, mics); +} + +// Test subtracting with microsecond underflow +TEST_F(Quantity_PeriodTest, SubtractWithMicrosecondUnderflow) +{ + Quantity_Period aPeriod1(0, 0, 0, 1, 0, 200000); // 1.2 seconds + Quantity_Period aPeriod2(0, 0, 0, 0, 0, 500000); // 0.5 seconds + + Quantity_Period aResult = aPeriod1.Subtract(aPeriod2); + + Standard_Integer ss, mics; + aResult.Values(ss, mics); + + EXPECT_EQ(0, ss); + EXPECT_EQ(700000, mics); // 1.2 - 0.5 = 0.7 seconds +} + +// Test subtracting larger from smaller (absolute value) +TEST_F(Quantity_PeriodTest, SubtractNegative) +{ + Quantity_Period aPeriod1(0, 1, 0, 0, 0, 0); // 1 hour + Quantity_Period aPeriod2(0, 3, 0, 0, 0, 0); // 3 hours + + Quantity_Period aResult = aPeriod1.Subtract(aPeriod2); + + Standard_Integer ss, mics; + aResult.Values(ss, mics); + + // Result should be absolute value (2 hours) + EXPECT_EQ(7200, ss); // 2 * 3600 + EXPECT_EQ(0, mics); +} + +// Test complex period calculations +TEST_F(Quantity_PeriodTest, ComplexCalculations) +{ + // 1 day, 2 hours, 30 minutes, 45 seconds, 500 milliseconds, 250 microseconds + Quantity_Period aPeriod(1, 2, 30, 45, 500, 250); + + Standard_Integer ss, mics; + aPeriod.Values(ss, mics); + + // Calculate expected seconds: + // 1 day = 86400 seconds + // 2 hours = 7200 seconds + // 30 minutes = 1800 seconds + // 45 seconds = 45 seconds + // Total = 95445 seconds + EXPECT_EQ(95445, ss); + + // Calculate expected microseconds: + // 500 milliseconds = 500000 microseconds + // 250 microseconds = 250 microseconds + // Total = 500250 microseconds + EXPECT_EQ(500250, mics); +} + +// Test extraction back to components (uses all time constants) +TEST_F(Quantity_PeriodTest, ComponentExtraction) +{ + Quantity_Period aPeriod(2, 3, 45, 30, 123, 456); + + Standard_Integer dd, hh, mn, ss, mis, mics; + aPeriod.Values(dd, hh, mn, ss, mis, mics); + + EXPECT_EQ(2, dd); + EXPECT_EQ(3, hh); + EXPECT_EQ(45, mn); + EXPECT_EQ(30, ss); + EXPECT_EQ(123, mis); + EXPECT_EQ(456, mics); +} + +// Test zero period +TEST_F(Quantity_PeriodTest, ZeroPeriod) +{ + Quantity_Period aPeriod(0, 0, 0, 0, 0, 0); + + Standard_Integer dd, hh, mn, ss, mis, mics; + aPeriod.Values(dd, hh, mn, ss, mis, mics); + + EXPECT_EQ(0, dd); + EXPECT_EQ(0, hh); + EXPECT_EQ(0, mn); + EXPECT_EQ(0, ss); + EXPECT_EQ(0, mis); + EXPECT_EQ(0, mics); + + // Zero period should be equal to itself + Quantity_Period aZero2(0, 0, 0, 0, 0, 0); + EXPECT_TRUE(aPeriod.IsEqual(aZero2)); +} + +// Test large values (stress test for constants) +TEST_F(Quantity_PeriodTest, LargeValues) +{ + // 100 days + Quantity_Period aPeriod(100, 0, 0, 0, 0, 0); + + Standard_Integer ss, mics; + aPeriod.Values(ss, mics); + + EXPECT_EQ(8640000, ss); // 100 * 86400 + EXPECT_EQ(0, mics); +} + +// Test specific time constant values (regression test) +TEST_F(Quantity_PeriodTest, TimeConstantValues) +{ + // Test that 24 hours = 1 day + Quantity_Period aPeriod24h(0, 24, 0, 0, 0, 0); + Quantity_Period aPeriod1d(1, 0, 0, 0, 0, 0); + + Standard_Integer ss1, mics1, ss2, mics2; + aPeriod24h.Values(ss1, mics1); + aPeriod1d.Values(ss2, mics2); + + EXPECT_EQ(ss1, ss2); // Should both be 86400 seconds + + // Test that 60 seconds = 1 minute + Quantity_Period aPeriod60s(0, 0, 0, 60, 0, 0); + Quantity_Period aPeriod1m(0, 0, 1, 0, 0, 0); + + aPeriod60s.Values(ss1, mics1); + aPeriod1m.Values(ss2, mics2); + + EXPECT_EQ(ss1, ss2); // Should both be 60 seconds + + // Test that 60 minutes = 1 hour + Quantity_Period aPeriod60m(0, 0, 60, 0, 0, 0); + Quantity_Period aPeriod1h(0, 1, 0, 0, 0, 0); + + aPeriod60m.Values(ss1, mics1); + aPeriod1h.Values(ss2, mics2); + + EXPECT_EQ(ss1, ss2); // Should both be 3600 seconds +} + +// Test that 1000 milliseconds = 1000000 microseconds = 1 second +TEST_F(Quantity_PeriodTest, MillisecondToSecondConversion) +{ + Quantity_Period aPeriod(0, 0, 0, 0, 1000, 0); + + Standard_Integer ss, mics; + aPeriod.Values(ss, mics); + + EXPECT_EQ(1, ss); // Should overflow to 1 second + EXPECT_EQ(0, mics); // No remaining microseconds +} + +// Test period subtraction with large microsecond underflow (verifies O(1) optimization) +TEST_F(Quantity_PeriodTest, SubtractLargeMicrosecondUnderflow) +{ + Quantity_Period aPeriod1(0, 0, 0, 10, 0, 200000); // 10.2 seconds + Quantity_Period aPeriod2(0, 0, 0, 0, 0, 2700000); // 2.7 seconds + + Quantity_Period aResult = aPeriod1.Subtract(aPeriod2); + + Standard_Integer ss, mics; + aResult.Values(ss, mics); + + EXPECT_EQ(7, ss); // 10.2 - 2.7 = 7.5 seconds + EXPECT_EQ(500000, mics); +} + +// Test period subtraction with negative seconds and positive microseconds +TEST_F(Quantity_PeriodTest, SubtractNegativeWithMicroseconds) +{ + Quantity_Period aPeriod1(0, 0, 0, 5, 0, 300000); // 5.3 seconds + Quantity_Period aPeriod2(0, 0, 0, 8, 0, 100000); // 8.1 seconds + + Quantity_Period aResult = aPeriod1.Subtract(aPeriod2); + + Standard_Integer ss, mics; + aResult.Values(ss, mics); + + // Absolute value: 8.1 - 5.3 = 2.8 seconds + EXPECT_EQ(2, ss); + EXPECT_EQ(800000, mics); +} diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_Color.cxx b/src/FoundationClasses/TKernel/Quantity/Quantity_Color.cxx index 60a948a6eb..fde68e2cd0 100644 --- a/src/FoundationClasses/TKernel/Quantity/Quantity_Color.cxx +++ b/src/FoundationClasses/TKernel/Quantity/Quantity_Color.cxx @@ -21,38 +21,91 @@ #include #include -#define RGBHLS_H_UNDEFINED -1.0 +namespace +{ +static constexpr float RGBHLS_H_UNDEFINED = -1.0f; +static constexpr double DEG_TO_RAD = M_PI / 180.0; +static constexpr double RAD_TO_DEG = 180.0 / M_PI; +static constexpr double POW_25_7 = 6103515625.0; // 25^7 used in CIEDE2000 +static constexpr double CIELAB_EPSILON = 0.008856451679035631; // (6/29)^3 +static constexpr double CIELAB_KAPPA = 7.787037037037037; // (1/3) * (29/6)^2 +static constexpr double CIELAB_OFFSET = 16.0 / 116.0; +static constexpr double CIELAB_L_COEFF = 116.0; +static constexpr double CIELAB_A_COEFF = 500.0; +static constexpr double CIELAB_B_COEFF = 200.0; +static constexpr double CIELAB_L_OFFSET = 16.0; +// D65 / 2 deg (CIE 1931) standard illuminant reference white point +static constexpr double D65_REF_X = 95.047; +static constexpr double D65_REF_Y = 100.000; +static constexpr double D65_REF_Z = 108.883; +// sRGB to XYZ conversion matrix (D65 illuminant, 2 deg observer) +static constexpr double RGB_TO_XYZ_R_X = 0.4124564; +static constexpr double RGB_TO_XYZ_R_Y = 0.2126729; +static constexpr double RGB_TO_XYZ_R_Z = 0.0193339; +static constexpr double RGB_TO_XYZ_G_X = 0.3575761; +static constexpr double RGB_TO_XYZ_G_Y = 0.7151522; +static constexpr double RGB_TO_XYZ_G_Z = 0.1191920; +static constexpr double RGB_TO_XYZ_B_X = 0.1804375; +static constexpr double RGB_TO_XYZ_B_Y = 0.0721750; +static constexpr double RGB_TO_XYZ_B_Z = 0.9503041; +// XYZ to sRGB conversion matrix (D65 illuminant, 2 deg observer) +static constexpr double XYZ_TO_RGB_X_R = 3.2404542; +static constexpr double XYZ_TO_RGB_X_G = -0.9692660; +static constexpr double XYZ_TO_RGB_X_B = 0.0556434; +static constexpr double XYZ_TO_RGB_Y_R = -1.5371385; +static constexpr double XYZ_TO_RGB_Y_G = 1.8760108; +static constexpr double XYZ_TO_RGB_Y_B = -0.2040259; +static constexpr double XYZ_TO_RGB_Z_R = -0.4985314; +static constexpr double XYZ_TO_RGB_Z_G = 0.0415560; +static constexpr double XYZ_TO_RGB_Z_B = 1.0572252; +} // namespace -static Standard_Real TheEpsilon = 0.0001; +namespace +{ +// Returns a reference to the epsilon value. +inline Standard_Real& getEpsilonRef() noexcept +{ + static Standard_Real theEpsilon = 0.0001; + return theEpsilon; +} -// Throw exception if RGB values are out of range. -#define Quantity_ColorValidateRgbRange(theR, theG, theB) \ - if (theR < 0.0 || theR > 1.0 || theG < 0.0 || theG > 1.0 || theB < 0.0 || theB > 1.0) \ - { \ - throw Standard_OutOfRange("Color out"); \ +// Validate RGB values are in range [0, 1]. +inline void validateRgbRange(Standard_Real theR, Standard_Real theG, Standard_Real theB) +{ + if (theR < 0.0 || theR > 1.0 || theG < 0.0 || theG > 1.0 || theB < 0.0 || theB > 1.0) + { + throw Standard_OutOfRange("Color out"); } +} -// Throw exception if HLS values are out of range. -#define Quantity_ColorValidateHlsRange(theH, theL, theS) \ - if ((theH < 0.0 && theH != RGBHLS_H_UNDEFINED && theS != 0.0) || (theH > 360.0) || theL < 0.0 \ - || theL > 1.0 || theS < 0.0 || theS > 1.0) \ - { \ - throw Standard_OutOfRange("Color out"); \ +// Validate HLS values are in valid ranges. +inline void validateHlsRange(Standard_Real theH, Standard_Real theL, Standard_Real theS) +{ + if ((theH < 0.0 && theH != RGBHLS_H_UNDEFINED && theS != 0.0) || (theH > 360.0) || theL < 0.0 + || theL > 1.0 || theS < 0.0 || theS > 1.0) + { + throw Standard_OutOfRange("Color out"); } +} -// Throw exception if CIELab color values are out of range. -#define Quantity_ColorValidateLabRange(theL, thea, theb) \ - if (theL < 0. || theL > 100. || thea < -100. || thea > 100. || theb < -110. || theb > 100.) \ - { \ - throw Standard_OutOfRange("Color out"); \ +// Validate CIELab color values are in valid ranges. +inline void validateLabRange(Standard_Real theL, Standard_Real thea, Standard_Real theb) +{ + if (theL < 0. || theL > 100. || thea < -100. || thea > 100. || theb < -110. || theb > 100.) + { + throw Standard_OutOfRange("Color out"); } +} -// Throw exception if CIELch color values are out of range. -#define Quantity_ColorValidateLchRange(theL, thec, theh) \ - if (theL < 0. || theL > 100. || thec < 0. || thec > 135. || theh < 0.0 || theh > 360.) \ - { \ - throw Standard_OutOfRange("Color out"); \ +// Validate CIELch color values are in valid ranges. +inline void validateLchRange(Standard_Real theL, Standard_Real thec, Standard_Real theh) +{ + if (theL < 0. || theL > 100. || thec < 0. || thec > 135. || theh < 0.0 || theh > 360.) + { + throw Standard_OutOfRange("Color out"); } +} +} // anonymous namespace namespace { @@ -64,10 +117,10 @@ struct Quantity_StandardColor NCollection_Vec3 RgbValues; Quantity_NameOfColor EnumName; - Quantity_StandardColor(Quantity_NameOfColor theName, - const char* theStringName, - const NCollection_Vec3& thesRGB, - const NCollection_Vec3& theRGB) + constexpr Quantity_StandardColor(Quantity_NameOfColor theName, + const char* theStringName, + const NCollection_Vec3& thesRGB, + const NCollection_Vec3& theRGB) noexcept : StringName(theStringName), sRgbValues(thesRGB), RgbValues(theRGB), @@ -85,22 +138,22 @@ struct Quantity_StandardColor NCollection_Vec3(theR##f, theG##f, theB##f)) //! Name list of standard materials (defined within enumeration). -static const Quantity_StandardColor THE_COLORS[] = { +static constexpr Quantity_StandardColor THE_COLORS[] = { #include "Quantity_ColorTable.pxx" }; //================================================================================================= -Standard_Real Quantity_Color::Epsilon() +Standard_Real Quantity_Color::Epsilon() noexcept { - return TheEpsilon; + return getEpsilonRef(); } //================================================================================================= -void Quantity_Color::SetEpsilon(const Standard_Real theEpsilon) +void Quantity_Color::SetEpsilon(const Standard_Real theEpsilon) noexcept { - TheEpsilon = theEpsilon; + getEpsilonRef() = theEpsilon; } //================================================================================================= @@ -132,11 +185,11 @@ NCollection_Vec3 Quantity_Color::valuesOf(const Quantity_NameOfColor theN //================================================================================================= -Standard_CString Quantity_Color::StringName(const Quantity_NameOfColor theName) +Standard_CString Quantity_Color::StringName(const Quantity_NameOfColor theName) noexcept { if ((Standard_Integer)theName < 0 || (Standard_Integer)theName > Quantity_NOC_WHITE) { - throw Standard_OutOfRange("Bad name"); + return "UNDEFINED"; } return THE_COLORS[theName].StringName; } @@ -144,7 +197,7 @@ Standard_CString Quantity_Color::StringName(const Quantity_NameOfColor theName) //================================================================================================= Standard_Boolean Quantity_Color::ColorFromName(const Standard_CString theName, - Quantity_NameOfColor& theColor) + Quantity_NameOfColor& theColor) noexcept { TCollection_AsciiString aName(theName); aName.UpperCase(); @@ -249,7 +302,7 @@ Quantity_Color::Quantity_Color(const Standard_Real theC1, Quantity_Color::Quantity_Color(const NCollection_Vec3& theRgb) : myRgb(theRgb) { - Quantity_ColorValidateRgbRange(theRgb.r(), theRgb.g(), theRgb.b()); + validateRgbRange(theRgb.r(), theRgb.g(), theRgb.b()); } //================================================================================================= @@ -286,31 +339,31 @@ void Quantity_Color::SetValues(const Standard_Real theC1, switch (theType) { case Quantity_TOC_RGB: { - Quantity_ColorValidateRgbRange(theC1, theC2, theC3); + validateRgbRange(theC1, theC2, theC3); myRgb.SetValues(float(theC1), float(theC2), float(theC3)); break; } case Quantity_TOC_sRGB: { - Quantity_ColorValidateRgbRange(theC1, theC2, theC3); + validateRgbRange(theC1, theC2, theC3); myRgb.SetValues((float)Convert_sRGB_To_LinearRGB(theC1), (float)Convert_sRGB_To_LinearRGB(theC2), (float)Convert_sRGB_To_LinearRGB(theC3)); break; } case Quantity_TOC_HLS: { - Quantity_ColorValidateHlsRange(theC1, theC2, theC3); + validateHlsRange(theC1, theC2, theC3); myRgb = Convert_HLS_To_LinearRGB(NCollection_Vec3(float(theC1), float(theC2), float(theC3))); break; } case Quantity_TOC_CIELab: { - Quantity_ColorValidateLabRange(theC1, theC2, theC3); + validateLabRange(theC1, theC2, theC3); myRgb = Convert_Lab_To_LinearRGB(NCollection_Vec3(float(theC1), float(theC2), float(theC3))); break; } case Quantity_TOC_CIELch: { - Quantity_ColorValidateLchRange(theC1, theC2, theC3); + validateLchRange(theC1, theC2, theC3); myRgb = Convert_Lab_To_LinearRGB( Convert_Lch_To_Lab(NCollection_Vec3(float(theC1), float(theC2), float(theC3)))); break; @@ -346,21 +399,20 @@ Standard_Real Quantity_Color::DeltaE2000(const Quantity_Color& theOther) const Standard_Real aLx_mean = 0.5 * (aL1 + aL2); // mean C - Standard_Real aC1 = Sqrt(aa1 * aa1 + ab1 * ab1); - Standard_Real aC2 = Sqrt(aa2 * aa2 + ab2 * ab2); - Standard_Real aC_mean = 0.5 * (aC1 + aC2); - Standard_Real aC_mean_pow7 = Pow(aC_mean, 7); - static const double a25_pow7 = Pow(25., 7); - Standard_Real aG = 0.5 * (1. - Sqrt(aC_mean_pow7 / (aC_mean_pow7 + a25_pow7))); - Standard_Real aa1x = aa1 * (1. + aG); - Standard_Real aa2x = aa2 * (1. + aG); - Standard_Real aC1x = Sqrt(aa1x * aa1x + ab1 * ab1); - Standard_Real aC2x = Sqrt(aa2x * aa2x + ab2 * ab2); - Standard_Real aCx_mean = 0.5 * (aC1x + aC2x); + Standard_Real aC1 = Sqrt(aa1 * aa1 + ab1 * ab1); + Standard_Real aC2 = Sqrt(aa2 * aa2 + ab2 * ab2); + Standard_Real aC_mean = 0.5 * (aC1 + aC2); + Standard_Real aC_mean_pow7 = Pow(aC_mean, 7); + Standard_Real aG = 0.5 * (1. - Sqrt(aC_mean_pow7 / (aC_mean_pow7 + POW_25_7))); + Standard_Real aa1x = aa1 * (1. + aG); + Standard_Real aa2x = aa2 * (1. + aG); + Standard_Real aC1x = Sqrt(aa1x * aa1x + ab1 * ab1); + Standard_Real aC2x = Sqrt(aa2x * aa2x + ab2 * ab2); + Standard_Real aCx_mean = 0.5 * (aC1x + aC2x); // mean H - Standard_Real ah1x = (aC1x > TheEpsilon ? ATan2(ab1, aa1x) * 180. / M_PI : 270.); - Standard_Real ah2x = (aC2x > TheEpsilon ? ATan2(ab2, aa2x) * 180. / M_PI : 270.); + Standard_Real ah1x = (aC1x > Epsilon() ? ATan2(ab1, aa1x) * RAD_TO_DEG : 270.); + Standard_Real ah2x = (aC2x > Epsilon() ? ATan2(ab2, aa2x) * RAD_TO_DEG : 270.); if (ah1x < 0.) ah1x += 360.; if (ah2x < 0.) @@ -376,13 +428,13 @@ Standard_Real Quantity_Color::DeltaE2000(const Quantity_Color& theOther) const // deltas Standard_Real aDeltaLx = aL2 - aL1; Standard_Real aDeltaCx = aC2x - aC1x; - Standard_Real aDeltaHx = 2. * Sqrt(aC1x * aC2x) * Sin(0.5 * aDeltahx * M_PI / 180.); + Standard_Real aDeltaHx = 2. * Sqrt(aC1x * aC2x) * Sin(0.5 * aDeltahx * DEG_TO_RAD); // factors - Standard_Real aT = 1. - 0.17 * Cos((aHx_mean - 30.) * M_PI / 180.) - + 0.24 * Cos((2. * aHx_mean) * M_PI / 180.) - + 0.32 * Cos((3. * aHx_mean + 6.) * M_PI / 180.) - - 0.20 * Cos((4. * aHx_mean - 63.) * M_PI / 180.); + Standard_Real aT = 1. - 0.17 * Cos((aHx_mean - 30.) * DEG_TO_RAD) + + 0.24 * Cos((2. * aHx_mean) * DEG_TO_RAD) + + 0.32 * Cos((3. * aHx_mean + 6.) * DEG_TO_RAD) + - 0.20 * Cos((4. * aHx_mean - 63.) * DEG_TO_RAD); Standard_Real aLx_mean50_2 = (aLx_mean - 50.) * (aLx_mean - 50.); Standard_Real aS_L = 1. + 0.015 * aLx_mean50_2 / Sqrt(20. + aLx_mean50_2); @@ -391,8 +443,8 @@ Standard_Real Quantity_Color::DeltaE2000(const Quantity_Color& theOther) const Standard_Real aDelta_theta = 30. * Exp(-(aHx_mean - 275.) * (aHx_mean - 275.) / 625.); Standard_Real aCx_mean_pow7 = Pow(aCx_mean, 7); - Standard_Real aR_C = 2. * Sqrt(aCx_mean_pow7 / (aCx_mean_pow7 + a25_pow7)); - Standard_Real aR_T = -aR_C * Sin(2. * aDelta_theta * M_PI / 180.); + Standard_Real aR_C = 2. * Sqrt(aCx_mean_pow7 / (aCx_mean_pow7 + POW_25_7)); + Standard_Real aR_T = -aR_C * Sin(2. * aDelta_theta * DEG_TO_RAD); // finally, the difference Standard_Real aDL = aDeltaLx / aS_L; @@ -523,7 +575,8 @@ NCollection_Vec3 Quantity_Color::Convert_HLS_To_sRGB(const NCollection_Ve // function : Convert_sRGB_To_HLS // purpose : Reference: La synthese d'images, Collection Hermes // ======================================================================= -NCollection_Vec3 Quantity_Color::Convert_sRGB_To_HLS(const NCollection_Vec3& theRgb) +NCollection_Vec3 Quantity_Color::Convert_sRGB_To_HLS( + const NCollection_Vec3& theRgb) noexcept { float aPlus = 0.0f; float aDiff = theRgb.g() - theRgb.b(); @@ -573,10 +626,10 @@ NCollection_Vec3 Quantity_Color::Convert_sRGB_To_HLS(const NCollection_Ve // purpose : non-linear function transforming XYZ coordinates to CIE Lab // see http://www.brucelindbloom.com/index.html?Equations.html // ======================================================================= -static inline double CIELab_f(double theValue) +static inline double CIELab_f(double theValue) noexcept { - return theValue > 0.008856451679035631 ? Pow(theValue, 1. / 3.) - : (7.787037037037037 * theValue) + 16. / 116.; + return theValue > CIELAB_EPSILON ? Pow(theValue, 1. / 3.) + : (CIELAB_KAPPA * theValue) + CIELAB_OFFSET; } // ======================================================================= @@ -584,10 +637,10 @@ static inline double CIELab_f(double theValue) // purpose : inverse of non-linear function transforming XYZ coordinates to CIE Lab // see http://www.brucelindbloom.com/index.html?Equations.html // ======================================================================= -static inline double CIELab_invertf(double theValue) +static inline double CIELab_invertf(double theValue) noexcept { double aV3 = theValue * theValue * theValue; - return aV3 > 0.008856451679035631 ? aV3 : (theValue - 16. / 116.) / 7.787037037037037; + return aV3 > CIELAB_EPSILON ? aV3 : (theValue - CIELAB_OFFSET) / CIELAB_KAPPA; } // ======================================================================= @@ -596,7 +649,7 @@ static inline double CIELab_invertf(double theValue) // see https://www.easyrgb.com/en/math.php // ======================================================================= NCollection_Vec3 Quantity_Color::Convert_LinearRGB_To_Lab( - const NCollection_Vec3& theRgb) + const NCollection_Vec3& theRgb) noexcept { double aR = theRgb[0]; double aG = theRgb[1]; @@ -604,18 +657,18 @@ NCollection_Vec3 Quantity_Color::Convert_LinearRGB_To_Lab( // convert to XYZ normalized to D65 / 2 deg (CIE 1931) standard illuminant intensities // see http://www.brucelindbloom.com/index.html?Equations.html - double aX = (aR * 0.4124564 + aG * 0.3575761 + aB * 0.1804375) * 100. / 95.047; - double aY = (aR * 0.2126729 + aG * 0.7151522 + aB * 0.0721750) * 100. / 100.000; - double aZ = (aR * 0.0193339 + aG * 0.1191920 + aB * 0.9503041) * 100. / 108.883; + double aX = (aR * RGB_TO_XYZ_R_X + aG * RGB_TO_XYZ_G_X + aB * RGB_TO_XYZ_B_X) * 100. / D65_REF_X; + double aY = (aR * RGB_TO_XYZ_R_Y + aG * RGB_TO_XYZ_G_Y + aB * RGB_TO_XYZ_B_Y); + double aZ = (aR * RGB_TO_XYZ_R_Z + aG * RGB_TO_XYZ_G_Z + aB * RGB_TO_XYZ_B_Z) * 100. / D65_REF_Z; // convert to Lab double afX = CIELab_f(aX); double afY = CIELab_f(aY); double afZ = CIELab_f(aZ); - double aL = 116. * afY - 16.; - double aa = 500. * (afX - afY); - double ab = 200. * (afY - afZ); + double aL = CIELAB_L_COEFF * afY - CIELAB_L_OFFSET; + double aa = CIELAB_A_COEFF * (afX - afY); + double ab = CIELAB_B_COEFF * (afY - afZ); return NCollection_Vec3((float)aL, (float)aa, (float)ab); } @@ -626,7 +679,7 @@ NCollection_Vec3 Quantity_Color::Convert_LinearRGB_To_Lab( // see https://www.easyrgb.com/en/math.php // ======================================================================= NCollection_Vec3 Quantity_Color::Convert_Lab_To_LinearRGB( - const NCollection_Vec3& theLab) + const NCollection_Vec3& theLab) noexcept { double aL = theLab[0]; double aa = theLab[1]; @@ -643,19 +696,19 @@ NCollection_Vec3 Quantity_Color::Convert_Lab_To_LinearRGB( double aC = aRate / (double)NBSTEPS; // convert to XYZ for D65 / 2 deg (CIE 1931) standard illuminant - double afY = (aL + 16.) / 116.; - double afX = aC * aa / 500. + afY; - double afZ = afY - aC * ab / 200.; + double afY = (aL + CIELAB_L_OFFSET) / CIELAB_L_COEFF; + double afX = aC * aa / CIELAB_A_COEFF + afY; + double afZ = afY - aC * ab / CIELAB_B_COEFF; - double aX = CIELab_invertf(afX) * 95.047; - double aY = CIELab_invertf(afY) * 100.000; - double aZ = CIELab_invertf(afZ) * 108.883; + double aX = CIELab_invertf(afX) * D65_REF_X; + double aY = CIELab_invertf(afY) * D65_REF_Y; + double aZ = CIELab_invertf(afZ) * D65_REF_Z; // convert to RGB // see http://www.brucelindbloom.com/index.html?Equations.html - double aR = (aX * 3.2404542 + aY * -1.5371385 + aZ * -0.4985314) / 100.; - double aG = (aX * -0.9692660 + aY * 1.8760108 + aZ * 0.0415560) / 100.; - double aB = (aX * 0.0556434 + aY * -0.2040259 + aZ * 1.0572252) / 100.; + double aR = (aX * XYZ_TO_RGB_X_R + aY * XYZ_TO_RGB_Y_R + aZ * XYZ_TO_RGB_Z_R) / 100.; + double aG = (aX * XYZ_TO_RGB_X_G + aY * XYZ_TO_RGB_Y_G + aZ * XYZ_TO_RGB_Z_G) / 100.; + double aB = (aX * XYZ_TO_RGB_X_B + aY * XYZ_TO_RGB_Y_B + aZ * XYZ_TO_RGB_Z_B) / 100.; // exit if we are in range or at zero C if (aRate == 0 || (aR >= 0. && aR <= 1. && aG >= 0. && aG <= 1. && aB >= 0. && aB <= 1.)) @@ -670,13 +723,14 @@ NCollection_Vec3 Quantity_Color::Convert_Lab_To_LinearRGB( // purpose : convert CIE Lab color to CIE Lch color // see https://www.easyrgb.com/en/math.php // ======================================================================= -NCollection_Vec3 Quantity_Color::Convert_Lab_To_Lch(const NCollection_Vec3& theLab) +NCollection_Vec3 Quantity_Color::Convert_Lab_To_Lch( + const NCollection_Vec3& theLab) noexcept { double aa = theLab[1]; double ab = theLab[2]; double aC = Sqrt(aa * aa + ab * ab); - double aH = (aC > TheEpsilon ? ATan2(ab, aa) * 180. / M_PI : 0.); + double aH = (aC > Epsilon() ? ATan2(ab, aa) * RAD_TO_DEG : 0.); if (aH < 0.) aH += 360.; @@ -689,12 +743,13 @@ NCollection_Vec3 Quantity_Color::Convert_Lab_To_Lch(const NCollection_Vec // purpose : convert CIE Lch color to CIE Lab color // see https://www.easyrgb.com/en/math.php // ======================================================================= -NCollection_Vec3 Quantity_Color::Convert_Lch_To_Lab(const NCollection_Vec3& theLch) +NCollection_Vec3 Quantity_Color::Convert_Lch_To_Lab( + const NCollection_Vec3& theLch) noexcept { double aC = theLch[1]; double aH = theLch[2]; - aH *= M_PI / 180.; + aH *= DEG_TO_RAD; double aa = aC * Cos(aH); double ab = aC * Sin(aH); diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_Color.hxx b/src/FoundationClasses/TKernel/Quantity/Quantity_Color.hxx index a01ab8d568..6e818833d4 100644 --- a/src/FoundationClasses/TKernel/Quantity/Quantity_Color.hxx +++ b/src/FoundationClasses/TKernel/Quantity/Quantity_Color.hxx @@ -65,16 +65,16 @@ public: Standard_EXPORT Quantity_NameOfColor Name() const; //! Updates the color from specified named color. - void SetValues(const Quantity_NameOfColor theName) + void SetValues(const Quantity_NameOfColor theName) noexcept { myRgb = valuesOf(theName, Quantity_TOC_RGB); } //! Return the color as vector of 3 float elements. - const NCollection_Vec3& Rgb() const { return myRgb; } + constexpr const NCollection_Vec3& Rgb() const noexcept { return myRgb; } //! Return the color as vector of 3 float elements. - operator const NCollection_Vec3&() const { return myRgb; } + constexpr operator const NCollection_Vec3&() const noexcept { return myRgb; } //! Returns in theC1, theC2 and theC3 the components of this color //! according to the color system definition theType. @@ -91,21 +91,21 @@ public: const Quantity_TypeOfColor theType); //! Returns the Red component (quantity of red) of the color within range [0.0; 1.0]. - Standard_Real Red() const { return myRgb.r(); } + constexpr Standard_Real Red() const noexcept { return myRgb.r(); } //! Returns the Green component (quantity of green) of the color within range [0.0; 1.0]. - Standard_Real Green() const { return myRgb.g(); } + constexpr Standard_Real Green() const noexcept { return myRgb.g(); } //! Returns the Blue component (quantity of blue) of the color within range [0.0; 1.0]. - Standard_Real Blue() const { return myRgb.b(); } + constexpr Standard_Real Blue() const noexcept { return myRgb.b(); } //! Returns the Hue component (hue angle) of the color //! in degrees within range [0.0; 360.0], 0.0 being Red. //! -1.0 is a special value reserved for grayscale color (S should be 0.0) - Standard_Real Hue() const { return Convert_LinearRGB_To_HLS(myRgb)[0]; } + Standard_Real Hue() const noexcept { return Convert_LinearRGB_To_HLS(myRgb)[0]; } //! Returns the Light component (value of the lightness) of the color within range [0.0; 1.0]. - Standard_Real Light() const { return Convert_LinearRGB_To_HLS(myRgb)[1]; } + Standard_Real Light() const noexcept { return Convert_LinearRGB_To_HLS(myRgb)[1]; } //! Increases or decreases the intensity (variation of the lightness). //! The delta is a percentage. Any value greater than zero will increase the intensity. @@ -114,7 +114,7 @@ public: //! Returns the Saturation component (value of the saturation) of the color within range //! [0.0; 1.0]. - Standard_Real Saturation() const { return Convert_LinearRGB_To_HLS(myRgb)[2]; } + Standard_Real Saturation() const noexcept { return Convert_LinearRGB_To_HLS(myRgb)[2]; } //! Increases or decreases the contrast (variation of the saturation). //! The delta is a percentage. Any value greater than zero will increase the contrast. @@ -122,29 +122,32 @@ public: Standard_EXPORT void ChangeContrast(const Standard_Real theDelta); //! Returns TRUE if the distance between two colors is greater than Epsilon(). - Standard_Boolean IsDifferent(const Quantity_Color& theOther) const + Standard_Boolean IsDifferent(const Quantity_Color& theOther) const noexcept { return (SquareDistance(theOther) > Epsilon() * Epsilon()); } //! Alias to IsDifferent(). - Standard_Boolean operator!=(const Quantity_Color& theOther) const + Standard_Boolean operator!=(const Quantity_Color& theOther) const noexcept { return IsDifferent(theOther); } //! Returns TRUE if the distance between two colors is no greater than Epsilon(). - Standard_Boolean IsEqual(const Quantity_Color& theOther) const + Standard_Boolean IsEqual(const Quantity_Color& theOther) const noexcept { return (SquareDistance(theOther) <= Epsilon() * Epsilon()); } //! Alias to IsEqual(). - Standard_Boolean operator==(const Quantity_Color& theOther) const { return IsEqual(theOther); } + Standard_Boolean operator==(const Quantity_Color& theOther) const noexcept + { + return IsEqual(theOther); + } //! Returns the distance between two colors. It's a value between 0 and the square root of 3 (the //! black/white distance). - Standard_Real Distance(const Quantity_Color& theColor) const + Standard_Real Distance(const Quantity_Color& theColor) const noexcept { return (NCollection_Vec3(myRgb) - NCollection_Vec3(theColor.myRgb)) @@ -152,7 +155,7 @@ public: } //! Returns the square of distance between two colors. - Standard_Real SquareDistance(const Quantity_Color& theColor) const + Standard_Real SquareDistance(const Quantity_Color& theColor) const noexcept { return (NCollection_Vec3(myRgb) - NCollection_Vec3(theColor.myRgb)) @@ -186,20 +189,20 @@ public: } //! Returns the name of the color identified by the given Quantity_NameOfColor enumeration value. - Standard_EXPORT static Standard_CString StringName(const Quantity_NameOfColor theColor); + Standard_EXPORT static Standard_CString StringName(const Quantity_NameOfColor theColor) noexcept; //! Finds color from predefined names. //! For example, the name of the color which corresponds to "BLACK" is Quantity_NOC_BLACK. //! Returns FALSE if name is unknown. Standard_EXPORT static Standard_Boolean ColorFromName(const Standard_CString theName, - Quantity_NameOfColor& theColor); + Quantity_NameOfColor& theColor) noexcept; //! Finds color from predefined names. //! @param theColorNameString the color name //! @param theColor a found color //! @return false if the color name is unknown, or true if the search by color name was successful static Standard_Boolean ColorFromName(const Standard_CString theColorNameString, - Quantity_Color& theColor) + Quantity_Color& theColor) noexcept { Quantity_NameOfColor aColorName = Quantity_NOC_BLACK; if (!ColorFromName(theColorNameString, aColorName)) @@ -223,7 +226,7 @@ public: //! Returns hex sRGB string in format "#FFAAFF". static TCollection_AsciiString ColorToHex(const Quantity_Color& theColor, - const bool theToPrefixHash = true) + const bool theToPrefixHash = true) noexcept { NCollection_Vec3 anSRgb = Convert_LinearRGB_To_sRGB((NCollection_Vec3)theColor); @@ -240,40 +243,42 @@ public: //! Converts sRGB components into HLS ones. Standard_EXPORT static NCollection_Vec3 Convert_sRGB_To_HLS( - const NCollection_Vec3& theRgb); + const NCollection_Vec3& theRgb) noexcept; //! Converts HLS components into RGB ones. Standard_EXPORT static NCollection_Vec3 Convert_HLS_To_sRGB( const NCollection_Vec3& theHls); //! Converts Linear RGB components into HLS ones. - static NCollection_Vec3 Convert_LinearRGB_To_HLS(const NCollection_Vec3& theRgb) + static NCollection_Vec3 Convert_LinearRGB_To_HLS( + const NCollection_Vec3& theRgb) noexcept { return Convert_sRGB_To_HLS(Convert_LinearRGB_To_sRGB(theRgb)); } //! Converts HLS components into linear RGB ones. - static NCollection_Vec3 Convert_HLS_To_LinearRGB(const NCollection_Vec3& theHls) + static NCollection_Vec3 Convert_HLS_To_LinearRGB( + const NCollection_Vec3& theHls) noexcept { return Convert_sRGB_To_LinearRGB(Convert_HLS_To_sRGB(theHls)); } //! Converts linear RGB components into CIE Lab ones. Standard_EXPORT static NCollection_Vec3 Convert_LinearRGB_To_Lab( - const NCollection_Vec3& theRgb); + const NCollection_Vec3& theRgb) noexcept; //! Converts CIE Lab components into CIE Lch ones. Standard_EXPORT static NCollection_Vec3 Convert_Lab_To_Lch( - const NCollection_Vec3& theLab); + const NCollection_Vec3& theLab) noexcept; //! Converts CIE Lab components into linear RGB ones. //! Note that the resulting values may be out of the valid range for RGB. Standard_EXPORT static NCollection_Vec3 Convert_Lab_To_LinearRGB( - const NCollection_Vec3& theLab); + const NCollection_Vec3& theLab) noexcept; //! Converts CIE Lch components into CIE Lab ones. Standard_EXPORT static NCollection_Vec3 Convert_Lch_To_Lab( - const NCollection_Vec3& theLch); + const NCollection_Vec3& theLch) noexcept; //! Convert the color value to ARGB integer value, with alpha equals to 0. //! So the output is formatted as 0x00RRGGBB. @@ -281,7 +286,8 @@ public: //! as would be usually expected for RGB color packed into 4 bytes. //! @param[in] theColor color to convert //! @param[out] theARGB result color encoded as integer - static void Color2argb(const Quantity_Color& theColor, Standard_Integer& theARGB) + static constexpr void Color2argb(const Quantity_Color& theColor, + Standard_Integer& theARGB) noexcept { const NCollection_Vec3 aColor( static_cast(255.0f * theColor.myRgb.r() + 0.5f), @@ -293,7 +299,7 @@ public: //! Convert integer ARGB value to Color. Alpha bits are ignored. //! Note that this packing does NOT involve linear -> non-linear sRGB conversion, //! as would be usually expected to preserve higher (for human eye) color precision in 4 bytes. - static void Argb2color(const Standard_Integer theARGB, Quantity_Color& theColor) + static void Argb2color(const Standard_Integer theARGB, Quantity_Color& theColor) noexcept { const NCollection_Vec3 aColor( static_cast((theARGB & 0xff0000) >> 16), @@ -307,7 +313,7 @@ public: //! Convert linear RGB component into sRGB using OpenGL specs formula (double precision), also //! known as gamma correction. - static Standard_Real Convert_LinearRGB_To_sRGB(Standard_Real theLinearValue) + static Standard_Real Convert_LinearRGB_To_sRGB(Standard_Real theLinearValue) noexcept { return theLinearValue <= 0.0031308 ? theLinearValue * 12.92 : Pow(theLinearValue, 1.0 / 2.4) * 1.055 - 0.055; @@ -315,7 +321,7 @@ public: //! Convert linear RGB component into sRGB using OpenGL specs formula (single precision), also //! known as gamma correction. - static float Convert_LinearRGB_To_sRGB(float theLinearValue) + static float Convert_LinearRGB_To_sRGB(float theLinearValue) noexcept { return theLinearValue <= 0.0031308f ? theLinearValue * 12.92f : powf(theLinearValue, 1.0f / 2.4f) * 1.055f - 0.055f; @@ -323,7 +329,7 @@ public: //! Convert sRGB component into linear RGB using OpenGL specs formula (double precision), also //! known as gamma correction. - static Standard_Real Convert_sRGB_To_LinearRGB(Standard_Real thesRGBValue) + static Standard_Real Convert_sRGB_To_LinearRGB(Standard_Real thesRGBValue) noexcept { return thesRGBValue <= 0.04045 ? thesRGBValue / 12.92 : Pow((thesRGBValue + 0.055) / 1.055, 2.4); @@ -331,7 +337,7 @@ public: //! Convert sRGB component into linear RGB using OpenGL specs formula (single precision), also //! known as gamma correction. - static float Convert_sRGB_To_LinearRGB(float thesRGBValue) + static float Convert_sRGB_To_LinearRGB(float thesRGBValue) noexcept { return thesRGBValue <= 0.04045f ? thesRGBValue / 12.92f : powf((thesRGBValue + 0.055f) / 1.055f, 2.4f); @@ -339,7 +345,7 @@ public: //! Convert linear RGB components into sRGB using OpenGL specs formula. template - static NCollection_Vec3 Convert_LinearRGB_To_sRGB(const NCollection_Vec3& theRGB) + static NCollection_Vec3 Convert_LinearRGB_To_sRGB(const NCollection_Vec3& theRGB) noexcept { return NCollection_Vec3(Convert_LinearRGB_To_sRGB(theRGB.r()), Convert_LinearRGB_To_sRGB(theRGB.g()), @@ -348,7 +354,7 @@ public: //! Convert sRGB components into linear RGB using OpenGL specs formula. template - static NCollection_Vec3 Convert_sRGB_To_LinearRGB(const NCollection_Vec3& theRGB) + static NCollection_Vec3 Convert_sRGB_To_LinearRGB(const NCollection_Vec3& theRGB) noexcept { return NCollection_Vec3(Convert_sRGB_To_LinearRGB(theRGB.r()), Convert_sRGB_To_LinearRGB(theRGB.g()), @@ -356,20 +362,20 @@ public: } //! Convert linear RGB component into sRGB using approximated uniform gamma coefficient 2.2. - static float Convert_LinearRGB_To_sRGB_approx22(float theLinearValue) + static float Convert_LinearRGB_To_sRGB_approx22(float theLinearValue) noexcept { return powf(theLinearValue, 2.2f); } //! Convert sRGB component into linear RGB using approximated uniform gamma coefficient 2.2 - static float Convert_sRGB_To_LinearRGB_approx22(float thesRGBValue) + static float Convert_sRGB_To_LinearRGB_approx22(float thesRGBValue) noexcept { return powf(thesRGBValue, 1.0f / 2.2f); } //! Convert linear RGB components into sRGB using approximated uniform gamma coefficient 2.2 static NCollection_Vec3 Convert_LinearRGB_To_sRGB_approx22( - const NCollection_Vec3& theRGB) + const NCollection_Vec3& theRGB) noexcept { return NCollection_Vec3(Convert_LinearRGB_To_sRGB_approx22(theRGB.r()), Convert_LinearRGB_To_sRGB_approx22(theRGB.g()), @@ -378,7 +384,7 @@ public: //! Convert sRGB components into linear RGB using approximated uniform gamma coefficient 2.2 static NCollection_Vec3 Convert_sRGB_To_LinearRGB_approx22( - const NCollection_Vec3& theRGB) + const NCollection_Vec3& theRGB) noexcept { return NCollection_Vec3(Convert_sRGB_To_LinearRGB_approx22(theRGB.r()), Convert_sRGB_To_LinearRGB_approx22(theRGB.g()), @@ -391,7 +397,7 @@ public: const Standard_Real theS, Standard_Real& theR, Standard_Real& theG, - Standard_Real& theB) + Standard_Real& theB) noexcept { const NCollection_Vec3 anRgb = Convert_HLS_To_sRGB(NCollection_Vec3((float)theH, (float)theL, (float)theS)); @@ -406,7 +412,7 @@ public: const Standard_Real theB, Standard_Real& theH, Standard_Real& theL, - Standard_Real& theS) + Standard_Real& theS) noexcept { const NCollection_Vec3 aHls = Convert_sRGB_To_HLS(NCollection_Vec3((float)theR, (float)theG, (float)theB)); @@ -417,10 +423,10 @@ public: public: //! Returns the value used to compare two colors for equality; 0.0001 by default. - Standard_EXPORT static Standard_Real Epsilon(); + Standard_EXPORT static Standard_Real Epsilon() noexcept; //! Set the value used to compare two colors for equality. - Standard_EXPORT static void SetEpsilon(const Standard_Real theEpsilon); + Standard_EXPORT static void SetEpsilon(const Standard_Real theEpsilon) noexcept; //! Dumps the content of me into the stream Standard_EXPORT void DumpJson(Standard_OStream& theOStream, Standard_Integer theDepth = -1) const; diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.cxx b/src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.cxx index 8de4958a9e..47dfd17c9c 100644 --- a/src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.cxx +++ b/src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.cxx @@ -35,6 +35,11 @@ enum HexColorLength HexColorLength_RGBA = 8 //!< RGBA hex color format }; +static constexpr ColorInteger HEX_BASE = 16; // Hexadecimal number base +static constexpr int HEX_BITS_PER_COMPONENT = 8; // 8 bits per component (256 values) +static constexpr int HEX_BITS_PER_COMPONENT_SHORT = 4; // 4 bits per component (16 values) +static constexpr int RGB_COMPONENT_LAST_INDEX = 2; // Last RGB component index (B in RGB) + //! Takes next color component from the integer representing a color (it is a step in a process of a //! conversion implemented by the function ConvertIntegerToColorRGBA) //! @param theColorInteger the integer representing a color @@ -77,7 +82,8 @@ static bool convertIntegerToColorRGBA(ColorInteger theColorInteger, takeColorComponentFromInteger(theColorInteger, theColorComponentBase); aColor.a() = anAlphaComponent; } - for (Standard_Integer aColorComponentIndex = 2; aColorComponentIndex >= 0; --aColorComponentIndex) + for (Standard_Integer aColorComponentIndex = RGB_COMPONENT_LAST_INDEX; aColorComponentIndex >= 0; + --aColorComponentIndex) { const Standard_ShortReal aColorComponent = takeColorComponentFromInteger(theColorInteger, theColorComponentBase); @@ -164,7 +170,7 @@ bool Quantity_ColorRGBA::ColorFromHex(const char* const theHexColorString, } ColorInteger aHexColorInteger; - if (!convertStringToInteger(aHexColorString, aHexColorInteger, 16u)) + if (!convertStringToInteger(aHexColorString, aHexColorInteger, HEX_BASE)) { return false; } @@ -197,8 +203,8 @@ bool Quantity_ColorRGBA::ColorFromHex(const char* const theHexColorString, return false; } - const ColorInteger THE_HEX_COLOR_COMPONENT_BASE = 1 << 8; - const ColorInteger THE_HEX_COLOR_COMPONENT_SHORT_BASE = 1 << 4; + const ColorInteger THE_HEX_COLOR_COMPONENT_BASE = 1 << HEX_BITS_PER_COMPONENT; + const ColorInteger THE_HEX_COLOR_COMPONENT_SHORT_BASE = 1 << HEX_BITS_PER_COMPONENT_SHORT; const ColorInteger aColorComponentBase = isShort ? THE_HEX_COLOR_COMPONENT_SHORT_BASE : THE_HEX_COLOR_COMPONENT_BASE; return convertIntegerToColorRGBA(aHexColorInteger, diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.hxx b/src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.hxx index c6a723c4dd..7b877e7926 100644 --- a/src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.hxx +++ b/src/FoundationClasses/TKernel/Quantity/Quantity_ColorRGBA.hxx @@ -28,14 +28,14 @@ public: } //! Creates the color with specified RGB value. - explicit Quantity_ColorRGBA(const Quantity_Color& theRgb) + constexpr explicit Quantity_ColorRGBA(const Quantity_Color& theRgb) : myRgb(theRgb), myAlpha(1.0f) { } //! Creates the color with specified RGBA values. - Quantity_ColorRGBA(const Quantity_Color& theRgb, float theAlpha) + constexpr Quantity_ColorRGBA(const Quantity_Color& theRgb, float theAlpha) : myRgb(theRgb), myAlpha(theAlpha) { @@ -56,49 +56,55 @@ public: } //! Assign new values to the color. - void SetValues(float theRed, float theGreen, float theBlue, float theAlpha) + void SetValues(float theRed, float theGreen, float theBlue, float theAlpha) noexcept { myRgb.SetValues(theRed, theGreen, theBlue, Quantity_TOC_RGB); myAlpha = theAlpha; } //! Return RGB color value. - const Quantity_Color& GetRGB() const { return myRgb; } + constexpr const Quantity_Color& GetRGB() const noexcept { return myRgb; } //! Modify RGB color components without affecting alpha value. - Quantity_Color& ChangeRGB() { return myRgb; } + constexpr Quantity_Color& ChangeRGB() noexcept { return myRgb; } //! Assign RGB color components without affecting alpha value. - void SetRGB(const Quantity_Color& theRgb) { myRgb = theRgb; } + constexpr void SetRGB(const Quantity_Color& theRgb) noexcept { myRgb = theRgb; } //! Return alpha value (1.0 means opaque, 0.0 means fully transparent). - Standard_ShortReal Alpha() const { return myAlpha; } + constexpr Standard_ShortReal Alpha() const noexcept { return myAlpha; } //! Assign the alpha value. - void SetAlpha(const Standard_ShortReal theAlpha) { myAlpha = theAlpha; } + constexpr void SetAlpha(const Standard_ShortReal theAlpha) noexcept { myAlpha = theAlpha; } //! Return the color as vector of 4 float elements. - operator const NCollection_Vec4&() const { return *(const NCollection_Vec4*)this; } + constexpr operator const NCollection_Vec4&() const noexcept + { + return *(const NCollection_Vec4*)this; + } //! Returns true if the distance between colors is greater than Epsilon(). - bool IsDifferent(const Quantity_ColorRGBA& theOther) const + bool IsDifferent(const Quantity_ColorRGBA& theOther) const noexcept { return myRgb.IsDifferent(theOther.GetRGB()) || Abs(myAlpha - theOther.myAlpha) > (float)Quantity_Color::Epsilon(); } //! Returns true if the distance between colors is greater than Epsilon(). - bool operator!=(const Quantity_ColorRGBA& theOther) const { return IsDifferent(theOther); } + bool operator!=(const Quantity_ColorRGBA& theOther) const noexcept + { + return IsDifferent(theOther); + } //! Two colors are considered to be equal if their distance is no greater than Epsilon(). - bool IsEqual(const Quantity_ColorRGBA& theOther) const + bool IsEqual(const Quantity_ColorRGBA& theOther) const noexcept { return myRgb.IsEqual(theOther.GetRGB()) && Abs(myAlpha - theOther.myAlpha) <= (float)Quantity_Color::Epsilon(); } //! Two colors are considered to be equal if their distance is no greater than Epsilon(). - bool operator==(const Quantity_ColorRGBA& theOther) const { return IsEqual(theOther); } + bool operator==(const Quantity_ColorRGBA& theOther) const noexcept { return IsEqual(theOther); } public: //! Finds color from predefined names. @@ -108,7 +114,7 @@ public: //! @param theColor a found color //! @return false if the color name is unknown, or true if the search by color name was successful static Standard_Boolean ColorFromName(const Standard_CString theColorNameString, - Quantity_ColorRGBA& theColor) + Quantity_ColorRGBA& theColor) noexcept { Quantity_ColorRGBA aColor; if (!Quantity_Color::ColorFromName(theColorNameString, aColor.ChangeRGB())) @@ -133,7 +139,7 @@ public: //! Returns hex sRGBA string in format "#RRGGBBAA". static TCollection_AsciiString ColorToHex(const Quantity_ColorRGBA& theColor, - const bool theToPrefixHash = true) + const bool theToPrefixHash = true) noexcept { NCollection_Vec4 anSRgb = Convert_LinearRGB_To_sRGB((NCollection_Vec4)theColor); @@ -151,7 +157,8 @@ public: public: //! Convert linear RGB components into sRGB using OpenGL specs formula. - static NCollection_Vec4 Convert_LinearRGB_To_sRGB(const NCollection_Vec4& theRGB) + static NCollection_Vec4 Convert_LinearRGB_To_sRGB( + const NCollection_Vec4& theRGB) noexcept { return NCollection_Vec4(Quantity_Color::Convert_LinearRGB_To_sRGB(theRGB.r()), Quantity_Color::Convert_LinearRGB_To_sRGB(theRGB.g()), @@ -160,7 +167,8 @@ public: } //! Convert sRGB components into linear RGB using OpenGL specs formula. - static NCollection_Vec4 Convert_sRGB_To_LinearRGB(const NCollection_Vec4& theRGB) + static NCollection_Vec4 Convert_sRGB_To_LinearRGB( + const NCollection_Vec4& theRGB) noexcept { return NCollection_Vec4(Quantity_Color::Convert_sRGB_To_LinearRGB(theRGB.r()), Quantity_Color::Convert_sRGB_To_LinearRGB(theRGB.g()), diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_Date.cxx b/src/FoundationClasses/TKernel/Quantity/Quantity_Date.cxx index bb60c726c3..ec46c6abae 100644 --- a/src/FoundationClasses/TKernel/Quantity/Quantity_Date.cxx +++ b/src/FoundationClasses/TKernel/Quantity/Quantity_Date.cxx @@ -22,21 +22,38 @@ #include #include -static int month_table[12] = {31, // January - 28, // February - 31, // March - 30, // April - 31, // May - 30, // June - 31, // July - 31, // August - 30, // September - 31, // October - 30, // November - 31}; // December - -static int SecondsByYear = 365 * 24 * 3600; // Normal Year -static int SecondsByLeapYear = 366 * 24 * 3600; // Leap Year +#include "Quantity_TimeConstants.pxx" + +namespace +{ + +static constexpr int month_table[12] = {31, // January + 28, // February + 31, // March + 30, // April + 31, // May + 30, // June + 31, // July + 31, // August + 30, // September + 31, // October + 30, // November + 31}; // December + +static constexpr int SecondsByYear = 365 * SECONDS_PER_DAY; // Normal Year +static constexpr int SecondsByLeapYear = 366 * SECONDS_PER_DAY; // Leap Year + +// Returns the number of days in a month for a given year (handles leap years) +constexpr Standard_Integer getDaysInMonth(const Standard_Integer theMonth, + const Standard_Integer theYear) noexcept +{ + if (theMonth == 2) + { + return Quantity_Date::IsLeap(theYear) ? 29 : 28; + } + return month_table[theMonth - 1]; +} +} // anonymous namespace // ----------------------------------------- // Initialize a date to January,1 1979 00:00 @@ -69,12 +86,7 @@ Standard_Boolean Quantity_Date::IsValid(const Standard_Integer mm, if (yy < 1979) return Standard_False; - if (Quantity_Date::IsLeap(yy)) - month_table[1] = 29; - else - month_table[1] = 28; - - if (dd < 1 || dd > month_table[mm - 1]) + if (dd < 1 || dd > getDaysInMonth(mm, yy)) return Standard_False; if (hh < 0 || hh > 23) @@ -135,11 +147,6 @@ void Quantity_Date::SetValues(const Standard_Integer mm, if (!Quantity_Date::IsValid(mm, dd, yy, hh, mn, ss, mis, mics)) throw Quantity_DateDefinitionError("Quantity_Date::Quantity_Date invalid parameters"); - if (Quantity_Date::IsLeap(yy)) - month_table[1] = 29; - else - month_table[1] = 28; - mySec = 0; myUSec = 0; for (i = 1979; i < yy; i++) @@ -152,18 +159,18 @@ void Quantity_Date::SetValues(const Standard_Integer mm, for (i = 1; i < mm; i++) { - mySec += month_table[i - 1] * 3600 * 24; + mySec += getDaysInMonth(i, yy) * SECONDS_PER_DAY; } - mySec += 3600 * 24 * (dd - 1); + mySec += SECONDS_PER_DAY * (dd - 1); - mySec += 3600 * hh; + mySec += SECONDS_PER_HOUR * hh; - mySec += 60 * mn; + mySec += SECONDS_PER_MINUTE * mn; mySec += ss; - myUSec += mis * 1000; + myUSec += mis * USECS_PER_MSEC; myUSec += mics; } @@ -183,13 +190,12 @@ void Quantity_Date::Values(Standard_Integer& mm, Standard_Integer& mics) const { - Standard_Integer i, carry; + Standard_Integer carry; for (yy = 1979, carry = mySec;; yy++) { if (!Quantity_Date::IsLeap(yy)) { - month_table[1] = 28; // normal year if (carry >= SecondsByYear) carry -= SecondsByYear; else @@ -197,7 +203,6 @@ void Quantity_Date::Values(Standard_Integer& mm, } else { - month_table[1] = 29; // Leap year if (carry >= SecondsByLeapYear) carry -= SecondsByLeapYear; else @@ -207,42 +212,20 @@ void Quantity_Date::Values(Standard_Integer& mm, for (mm = 1;; mm++) { - i = month_table[mm - 1] * 3600 * 24; + Standard_Integer i = getDaysInMonth(mm, yy) * SECONDS_PER_DAY; if (carry >= i) carry -= i; else break; } - i = 3600 * 24; - for (dd = 1;; dd++) - { - if (carry >= i) - carry -= i; - else - break; - } - - for (hh = 0;; hh++) - { - if (carry >= 3600) - carry -= 3600; - else - break; - } + // Extract day within the month + // carry holds seconds since the beginning of the current month + dd = carry / SECONDS_PER_DAY + 1; // Convert 0-based to 1-based day + carry -= (dd - 1) * SECONDS_PER_DAY; // Remove day component from carry - for (mn = 0;; mn++) - { - if (carry >= 60) - carry -= 60; - else - break; - } - - ss = carry; - - mis = myUSec / 1000; - mics = myUSec - (mis * 1000); + extractTimeComponents(carry, hh, mn, ss); + extractMillisAndMicros(myUSec, mis, mics); } // --------------------------------------------------------------------- @@ -252,9 +235,10 @@ void Quantity_Date::Values(Standard_Integer& mm, Quantity_Period Quantity_Date::Difference(const Quantity_Date& OtherDate) { - Standard_Integer i1, i2; + // Special case: if this date is the epoch (Jan 1, 1979 00:00), + // return OtherDate as a period (time elapsed since epoch) if (mySec == 0 && myUSec == 0) { i1 = OtherDate.mySec; @@ -266,28 +250,21 @@ Quantity_Period Quantity_Date::Difference(const Quantity_Date& OtherDate) i2 = myUSec - OtherDate.myUSec; } - if (i1 >= 0 && i2 < 0) - { - i1--; - i2 = 1000000 + i2; - } - else if (i1 < 0 && i2 >= 0) + // Normalize: handle microsecond underflow + normalizeSubtractionBorrow(i1, i2); + + // Period is always absolute value, convert negative result + if (i1 < 0) { - i1 = Abs(i1); + i1 = -i1; if (i2 > 0) { i1--; - i2 = 1000000 - i2; + i2 = USECS_PER_SEC - i2; } } - else if (i1 < 0 && i2 < 0) - { - i1 = Abs(i1); - i2 = Abs(i2); - } Quantity_Period result(i1, i2); - return (result); } @@ -308,11 +285,7 @@ Quantity_Date Quantity_Date::Subtract(const Quantity_Period& During) result.mySec -= ss; result.myUSec -= mics; - if (result.mySec >= 0 && result.myUSec < 0) - { - result.mySec--; - result.myUSec = 1000000 + result.myUSec; - } + normalizeSubtractionBorrow(result.mySec, result.myUSec); if (result.mySec < 0) throw Quantity_DateDefinitionError( @@ -332,11 +305,7 @@ Quantity_Date Quantity_Date::Add(const Quantity_Period& During) During.Values(result.mySec, result.myUSec); result.mySec += mySec; result.myUSec += myUSec; - if (result.myUSec >= 1000000) - { - result.mySec++; - result.myUSec -= 1000000; - } + normalizeAdditionOverflow(result.mySec, result.myUSec); return (result); } @@ -346,8 +315,8 @@ Quantity_Date Quantity_Date::Add(const Quantity_Period& During) // ---------------------------------------------------------------------- Standard_Integer Quantity_Date::Year() { - Standard_Integer dummy, year; - Values(dummy, dummy, year, dummy, dummy, dummy, dummy, dummy); + Standard_Integer mm, dd, year, hh, mn, ss, mis, mics; + Values(mm, dd, year, hh, mn, ss, mis, mics); return (year); } @@ -357,9 +326,9 @@ Standard_Integer Quantity_Date::Year() // ---------------------------------------------------------------------- Standard_Integer Quantity_Date::Month() { - Standard_Integer dummy, month; - Values(month, dummy, dummy, dummy, dummy, dummy, dummy, dummy); - return (month); + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + Values(mm, dd, yy, hh, mn, ss, mis, mics); + return (mm); } // ---------------------------------------------------------------------- @@ -369,9 +338,9 @@ Standard_Integer Quantity_Date::Month() Standard_Integer Quantity_Date::Day() { - Standard_Integer dummy, day; - Values(dummy, day, dummy, dummy, dummy, dummy, dummy, dummy); - return (day); + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + Values(mm, dd, yy, hh, mn, ss, mis, mics); + return (dd); } // ---------------------------------------------------------------------- @@ -381,9 +350,9 @@ Standard_Integer Quantity_Date::Day() Standard_Integer Quantity_Date::Hour() { - Standard_Integer dummy, hour; - Values(dummy, dummy, dummy, hour, dummy, dummy, dummy, dummy); - return (hour); + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + Values(mm, dd, yy, hh, mn, ss, mis, mics); + return (hh); } // ---------------------------------------------------------------------- @@ -393,9 +362,9 @@ Standard_Integer Quantity_Date::Hour() Standard_Integer Quantity_Date::Minute() { - Standard_Integer dummy, min; - Values(dummy, dummy, dummy, dummy, min, dummy, dummy, dummy); - return (min); + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + Values(mm, dd, yy, hh, mn, ss, mis, mics); + return (mn); } // ---------------------------------------------------------------------- @@ -405,9 +374,9 @@ Standard_Integer Quantity_Date::Minute() Standard_Integer Quantity_Date::Second() { - Standard_Integer dummy, sec; - Values(dummy, dummy, dummy, dummy, dummy, sec, dummy, dummy); - return (sec); + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + Values(mm, dd, yy, hh, mn, ss, mis, mics); + return (ss); } // ---------------------------------------------------------------------- @@ -417,9 +386,9 @@ Standard_Integer Quantity_Date::Second() Standard_Integer Quantity_Date::MilliSecond() { - Standard_Integer dummy, msec; - Values(dummy, dummy, dummy, dummy, dummy, dummy, msec, dummy); - return (msec); + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + Values(mm, dd, yy, hh, mn, ss, mis, mics); + return (mis); } // ---------------------------------------------------------------------- @@ -429,47 +398,7 @@ Standard_Integer Quantity_Date::MilliSecond() Standard_Integer Quantity_Date::MicroSecond() { - Standard_Integer dummy, msec; - Values(dummy, dummy, dummy, dummy, dummy, dummy, dummy, msec); - return (msec); -} - -// ---------------------------------------------------------------------- -// IsEarlier : Return true if the date is earlier than an other date -// ~~~~~~~~~ -// ---------------------------------------------------------------------- - -Standard_Boolean Quantity_Date::IsEarlier(const Quantity_Date& other) const -{ - if (mySec < other.mySec) - return Standard_True; - else if (mySec > other.mySec) - return Standard_False; - else - return ((myUSec < other.myUSec) ? Standard_True : Standard_False); -} - -// ---------------------------------------------------------------------- -// IsLater : Return true if the date is later than an other date -// ~~~~~~~ -// ---------------------------------------------------------------------- - -Standard_Boolean Quantity_Date::IsLater(const Quantity_Date& other) const -{ - if (mySec > other.mySec) - return Standard_True; - else if (mySec < other.mySec) - return Standard_False; - else - return ((myUSec > other.myUSec) ? Standard_True : Standard_False); -} - -// ---------------------------------------------------------------------- -// IsEqual : Return true if the date is the same than an other date -// ~~~~~~~ -// ---------------------------------------------------------------------- - -Standard_Boolean Quantity_Date::IsEqual(const Quantity_Date& other) const -{ - return ((myUSec == other.myUSec && mySec == other.mySec) ? Standard_True : Standard_False); + Standard_Integer mm, dd, yy, hh, mn, ss, mis, mics; + Values(mm, dd, yy, hh, mn, ss, mis, mics); + return (mics); } diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_Date.hxx b/src/FoundationClasses/TKernel/Quantity/Quantity_Date.hxx index 8230ea16df..ea5afdee3c 100644 --- a/src/FoundationClasses/TKernel/Quantity/Quantity_Date.hxx +++ b/src/FoundationClasses/TKernel/Quantity/Quantity_Date.hxx @@ -146,19 +146,37 @@ public: //! Returns TRUE if both and are equal. //! This method is an alias of operator ==. - Standard_EXPORT Standard_Boolean IsEqual(const Quantity_Date& anOther) const; + constexpr Standard_Boolean IsEqual(const Quantity_Date& anOther) const noexcept + { + return (myUSec == anOther.myUSec && mySec == anOther.mySec); + } - Standard_Boolean operator==(const Quantity_Date& anOther) const { return IsEqual(anOther); } + constexpr Standard_Boolean operator==(const Quantity_Date& anOther) const noexcept + { + return IsEqual(anOther); + } //! Returns TRUE if is earlier than . - Standard_EXPORT Standard_Boolean IsEarlier(const Quantity_Date& anOther) const; + constexpr Standard_Boolean IsEarlier(const Quantity_Date& anOther) const noexcept + { + return (mySec < anOther.mySec) || (mySec == anOther.mySec && myUSec < anOther.myUSec); + } - Standard_Boolean operator<(const Quantity_Date& anOther) const { return IsEarlier(anOther); } + constexpr Standard_Boolean operator<(const Quantity_Date& anOther) const noexcept + { + return IsEarlier(anOther); + } //! Returns TRUE if is later then . - Standard_EXPORT Standard_Boolean IsLater(const Quantity_Date& anOther) const; + constexpr Standard_Boolean IsLater(const Quantity_Date& anOther) const noexcept + { + return (mySec > anOther.mySec) || (mySec == anOther.mySec && myUSec > anOther.myUSec); + } - Standard_Boolean operator>(const Quantity_Date& anOther) const { return IsLater(anOther); } + constexpr Standard_Boolean operator>(const Quantity_Date& anOther) const noexcept + { + return IsLater(anOther); + } //! Checks the validity of a date - returns true if a //! date defined from the year yyyy, the month mm, @@ -189,7 +207,7 @@ public: //! Returns true if a year is a leap year. //! The leap years are divisible by 4 and not by 100 except //! the years divisible by 400. - static Standard_Boolean IsLeap(const Standard_Integer yy) + static constexpr Standard_Boolean IsLeap(const Standard_Integer yy) noexcept { return ((yy % 4 == 0) && (yy % 100 != 0)) || (yy % 400) == 0; } diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_Period.cxx b/src/FoundationClasses/TKernel/Quantity/Quantity_Period.cxx index 9a4f4c202f..6e91c0719f 100644 --- a/src/FoundationClasses/TKernel/Quantity/Quantity_Period.cxx +++ b/src/FoundationClasses/TKernel/Quantity/Quantity_Period.cxx @@ -21,6 +21,8 @@ #include #include +#include "Quantity_TimeConstants.pxx" + // ----------------------------------------------------------- // IsValid : Checks the validity of a date // With: @@ -38,9 +40,7 @@ Standard_Boolean Quantity_Period::IsValid(const Standard_Integer dd, const Standard_Integer mis, const Standard_Integer mics) { - - return ((dd < 0 || hh < 0 || mn < 0 || ss < 0 || mis < 0 || mics < 0) ? Standard_False - : Standard_True); + return (dd >= 0 && hh >= 0 && mn >= 0 && ss >= 0 && mis >= 0 && mics >= 0); } // ------------------------------------------------------------- @@ -51,8 +51,7 @@ Standard_Boolean Quantity_Period::IsValid(const Standard_Integer dd, // ------------------------------------------------------------- Standard_Boolean Quantity_Period::IsValid(const Standard_Integer ss, const Standard_Integer mics) { - - return ((ss < 0 || mics < 0) ? Standard_False : Standard_True); + return (ss >= 0 && mics >= 0); } // ------------------------------------------------------------- @@ -98,15 +97,10 @@ void Quantity_Period::Values(Standard_Integer& dd, Standard_Integer& mics) const { Standard_Integer carry = mySec; - dd = carry / (24 * 3600); - carry -= dd * 24 * 3600; - hh = carry / 3600; - carry -= 3600 * hh; - mn = carry / 60; - carry -= mn * 60; - ss = carry; - mis = myUSec / 1000; - mics = myUSec - (mis * 1000); + dd = carry / SECONDS_PER_DAY; + carry -= dd * SECONDS_PER_DAY; + extractTimeComponents(carry, hh, mn, ss); + extractMillisAndMicros(myUSec, mis, mics); } // ------------------------------------------------------------- @@ -131,7 +125,8 @@ void Quantity_Period::SetValues(const Standard_Integer dd, const Standard_Integer mils, const Standard_Integer mics) { - SetValues((dd * 24 * 3600) + (hh * 3600) + (60 * mn) + ss, mils * 1000 + mics); + SetValues((dd * SECONDS_PER_DAY) + (hh * SECONDS_PER_HOUR) + (SECONDS_PER_MINUTE * mn) + ss, + mils * USECS_PER_MSEC + mics); } // ------------------------------------------------------------- @@ -146,11 +141,7 @@ void Quantity_Period::SetValues(const Standard_Integer ss, const Standard_Intege mySec = ss; myUSec = mics; - while (myUSec > 1000000) - { - myUSec -= 1000000; - mySec++; - } + normalizeAdditionOverflow(mySec, myUSec); } // ------------------------------------------------------------- @@ -164,25 +155,19 @@ Quantity_Period Quantity_Period::Subtract(const Quantity_Period& OtherPeriod) co result.mySec -= OtherPeriod.mySec; result.myUSec -= OtherPeriod.myUSec; - if (result.mySec >= 0 && result.myUSec < 0) - { - result.mySec--; - result.myUSec = 1000000 + result.myUSec; - } - else if (result.mySec < 0 && result.myUSec >= 0) + normalizeSubtractionBorrow(result.mySec, result.myUSec); + + // Handle negative result (convert to absolute value) + // Note: after normalization, myUSec is always in [0, 999999] + if (result.mySec < 0) { result.mySec = Abs(result.mySec); if (result.myUSec > 0) { result.mySec--; - result.myUSec = 1000000 - result.myUSec; + result.myUSec = USECS_PER_SEC - result.myUSec; } } - else if (result.mySec < 0 && result.myUSec < 0) - { - result.mySec = Abs(result.mySec); - result.myUSec = Abs(result.myUSec); - } return (result); } @@ -196,51 +181,6 @@ Quantity_Period Quantity_Period::Add(const Quantity_Period& OtherPeriod) const Quantity_Period result(mySec, myUSec); result.mySec += OtherPeriod.mySec; result.myUSec += OtherPeriod.myUSec; - if (result.myUSec > 1000000) - { - result.myUSec -= 1000000; - result.mySec++; - } + normalizeAdditionOverflow(result.mySec, result.myUSec); return (result); } - -// ------------------------------------------------------------- -// IsEqual : returns true if two periods are equal -// ~~~~~~~ -// ------------------------------------------------------------- -Standard_Boolean Quantity_Period::IsEqual(const Quantity_Period& OtherPeriod) const -{ - - return ((mySec == OtherPeriod.mySec && myUSec == OtherPeriod.myUSec) ? Standard_True - : Standard_False); -} - -// ------------------------------------------------------------- -// IsShorter : returns true if a date is shorter then an other -// ~~~~~~~~~ date -// ------------------------------------------------------------- -Standard_Boolean Quantity_Period::IsShorter(const Quantity_Period& OtherPeriod) const -{ - - if (mySec < OtherPeriod.mySec) - return Standard_True; - else if (mySec > OtherPeriod.mySec) - return Standard_False; - else - return ((myUSec < OtherPeriod.myUSec) ? Standard_True : Standard_False); -} - -// ------------------------------------------------------------- -// IsLonger : returns true if a date is longer then an other -// ~~~~~~~~ date -// ------------------------------------------------------------- -Standard_Boolean Quantity_Period::IsLonger(const Quantity_Period& OtherPeriod) const -{ - - if (mySec > OtherPeriod.mySec) - return Standard_True; - else if (mySec < OtherPeriod.mySec) - return Standard_False; - else - return ((myUSec > OtherPeriod.myUSec) ? Standard_True : Standard_False); -} diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_Period.hxx b/src/FoundationClasses/TKernel/Quantity/Quantity_Period.hxx index 483b7e7642..e781c176c1 100644 --- a/src/FoundationClasses/TKernel/Quantity/Quantity_Period.hxx +++ b/src/FoundationClasses/TKernel/Quantity/Quantity_Period.hxx @@ -112,19 +112,37 @@ public: Quantity_Period operator+(const Quantity_Period& anOther) const { return Add(anOther); } //! Returns TRUE if both and are equal. - Standard_EXPORT Standard_Boolean IsEqual(const Quantity_Period& anOther) const; + constexpr Standard_Boolean IsEqual(const Quantity_Period& anOther) const noexcept + { + return (mySec == anOther.mySec && myUSec == anOther.myUSec); + } - Standard_Boolean operator==(const Quantity_Period& anOther) const { return IsEqual(anOther); } + constexpr Standard_Boolean operator==(const Quantity_Period& anOther) const noexcept + { + return IsEqual(anOther); + } //! Returns TRUE if is shorter than . - Standard_EXPORT Standard_Boolean IsShorter(const Quantity_Period& anOther) const; + constexpr Standard_Boolean IsShorter(const Quantity_Period& anOther) const noexcept + { + return (mySec < anOther.mySec) || (mySec == anOther.mySec && myUSec < anOther.myUSec); + } - Standard_Boolean operator<(const Quantity_Period& anOther) const { return IsShorter(anOther); } + constexpr Standard_Boolean operator<(const Quantity_Period& anOther) const noexcept + { + return IsShorter(anOther); + } //! Returns TRUE if is longer then . - Standard_EXPORT Standard_Boolean IsLonger(const Quantity_Period& anOther) const; - - Standard_Boolean operator>(const Quantity_Period& anOther) const { return IsLonger(anOther); } + constexpr Standard_Boolean IsLonger(const Quantity_Period& anOther) const noexcept + { + return (mySec > anOther.mySec) || (mySec == anOther.mySec && myUSec > anOther.myUSec); + } + + constexpr Standard_Boolean operator>(const Quantity_Period& anOther) const noexcept + { + return IsLonger(anOther); + } //! Checks the validity of a Period in form (dd,hh,mn,ss,mil,mic) //! With: diff --git a/src/FoundationClasses/TKernel/Quantity/Quantity_TimeConstants.pxx b/src/FoundationClasses/TKernel/Quantity/Quantity_TimeConstants.pxx new file mode 100644 index 0000000000..2add6dbe0a --- /dev/null +++ b/src/FoundationClasses/TKernel/Quantity/Quantity_TimeConstants.pxx @@ -0,0 +1,96 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Quantity_TimeConstants_HeaderFile +#define _Quantity_TimeConstants_HeaderFile + +//! @file Quantity_TimeConstants.pxx +//! Internal header providing shared time conversion constants and helper functions +//! for Quantity_Date and Quantity_Period classes. + +#include + +namespace +{ + +//! Time conversion constants +constexpr int SECONDS_PER_MINUTE = 60; +constexpr int SECONDS_PER_HOUR = 3600; // 60 * 60 +constexpr int SECONDS_PER_DAY = 86400; // 24 * 3600 + +//! Microsecond conversion constants +constexpr int USECS_PER_MSEC = 1000; // microseconds per millisecond +constexpr int USECS_PER_SEC = 1000000; // microseconds per second + +//! Extracts milliseconds and remaining microseconds from total microseconds +//! @param theUSec total microseconds +//! @param theMis output: milliseconds part +//! @param theMics output: remaining microseconds part +inline void extractMillisAndMicros(const Standard_Integer theUSec, + Standard_Integer& theMis, + Standard_Integer& theMics) noexcept +{ + theMis = theUSec / USECS_PER_MSEC; + theMics = theUSec - (theMis * USECS_PER_MSEC); +} + +//! Extracts hours, minutes, and seconds from remaining seconds in a day +//! @param theCarry input/output: seconds to extract from, updated with remainder +//! @param theHH output: hours +//! @param theMN output: minutes +//! @param theSS output: seconds +inline void extractTimeComponents(Standard_Integer& theCarry, + Standard_Integer& theHH, + Standard_Integer& theMN, + Standard_Integer& theSS) noexcept +{ + theHH = theCarry / SECONDS_PER_HOUR; + theCarry -= SECONDS_PER_HOUR * theHH; + theMN = theCarry / SECONDS_PER_MINUTE; + theCarry -= theMN * SECONDS_PER_MINUTE; + theSS = theCarry; +} + +//! Normalizes time values when microseconds overflow into seconds +//! (handles addition overflow: myUSec >= 1000000) +//! Uses division for O(1) complexity +//! @param theSec input/output: seconds, incremented if overflow occurs +//! @param theUSec input/output: microseconds, normalized to 0..999999 +inline void normalizeAdditionOverflow(Standard_Integer& theSec, Standard_Integer& theUSec) noexcept +{ + if (theUSec >= USECS_PER_SEC) + { + const Standard_Integer overflow = theUSec / USECS_PER_SEC; + theSec += overflow; + theUSec -= overflow * USECS_PER_SEC; + } +} + +//! Normalizes time values when microseconds require borrowing from seconds +//! (handles subtraction borrow: myUSec < 0) +//! Uses ceiling division for O(1) complexity +//! @param theSec input/output: seconds, decremented if borrow occurs +//! @param theUSec input/output: microseconds, normalized to 0..999999 +inline void normalizeSubtractionBorrow(Standard_Integer& theSec, Standard_Integer& theUSec) noexcept +{ + if (theUSec < 0) + { + const Standard_Integer borrow = (-theUSec + USECS_PER_SEC - 1) / USECS_PER_SEC; + theSec -= borrow; + theUSec += borrow * USECS_PER_SEC; + } +} + +} // anonymous namespace + +#endif // _Quantity_TimeConstants_HeaderFile -- 2.39.5