ImageComparison.cpp 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Atom/Utils/ImageComparison.h>
  9. #include <AzCore/Casting/numeric_cast.h>
  10. #include <AzCore/Preprocessor/EnumReflectUtils.h>
  11. namespace AZ
  12. {
  13. namespace Utils
  14. {
  15. void ImageComparisonError::Reflect(ReflectContext* context)
  16. {
  17. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  18. {
  19. serializeContext->Class<ImageComparisonError>()
  20. ->Version(1)
  21. ->Field("ErrorMessage", &ImageComparisonError::m_errorMessage);
  22. }
  23. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  24. {
  25. behaviorContext->Class<ImageComparisonError>("ImageComparisonError")
  26. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  27. ->Attribute(AZ::Script::Attributes::Module, "utils")
  28. ->Property("ErrorMessage", BehaviorValueProperty(&ImageComparisonError::m_errorMessage));
  29. }
  30. }
  31. void ImageDiffResult::Reflect(ReflectContext* context)
  32. {
  33. ImageComparisonError::Reflect(context);
  34. if (auto* serializeContext = azrtti_cast<SerializeContext*>(context))
  35. {
  36. serializeContext->Class<ImageDiffResult>()
  37. ->Version(1)
  38. ->Field("DiffScore", &ImageDiffResult::m_diffScore)
  39. ->Field("FilteredDiffScore", &ImageDiffResult::m_filteredDiffScore);
  40. }
  41. if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  42. {
  43. behaviorContext->Class<ImageDiffResult>("ImageDiffResult")
  44. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  45. ->Attribute(AZ::Script::Attributes::Module, "utils")
  46. ->Property("DiffScore", BehaviorValueProperty(&ImageDiffResult::m_diffScore))
  47. ->Attribute(AZ::Script::Attributes::Alias, "diff_score")
  48. ->Property("FilteredDiffScore", BehaviorValueProperty(&ImageDiffResult::m_filteredDiffScore))
  49. ->Attribute(AZ::Script::Attributes::Alias, "filtered_diff_score")
  50. ;
  51. }
  52. }
  53. int16_t CalcMaxChannelDifference(AZStd::span<const uint8_t> bufferA, AZStd::span<const uint8_t> bufferB, size_t index)
  54. {
  55. // We use the max error from a single channel instead of accumulating the error from each channel.
  56. // This normalizes differences so that for example black vs red has the same weight as black vs yellow.
  57. const int16_t diffR = static_cast<int16_t>(abs(aznumeric_cast<int16_t>(bufferA[index]) - aznumeric_cast<int16_t>(bufferB[index])));
  58. const int16_t diffG = static_cast<int16_t>(abs(aznumeric_cast<int16_t>(bufferA[index + 1]) - aznumeric_cast<int16_t>(bufferB[index + 1])));
  59. const int16_t diffB = static_cast<int16_t>(abs(aznumeric_cast<int16_t>(bufferA[index + 2]) - aznumeric_cast<int16_t>(bufferB[index + 2])));
  60. const int16_t diffA = static_cast<int16_t>(abs(aznumeric_cast<int16_t>(bufferA[index + 3]) - aznumeric_cast<int16_t>(bufferB[index + 3])));
  61. return AZ::GetMax(AZ::GetMax(AZ::GetMax(diffR, diffG), diffB), diffA);
  62. }
  63. AZ::Outcome<ImageDiffResult, ImageComparisonError> CalcImageDiffRms(
  64. AZStd::span<const uint8_t> bufferA, const RHI::Size& sizeA, RHI::Format formatA,
  65. AZStd::span<const uint8_t> bufferB, const RHI::Size& sizeB, RHI::Format formatB,
  66. float minDiffFilter)
  67. {
  68. static constexpr size_t BytesPerPixel = 4;
  69. ImageDiffResult result;
  70. ImageComparisonError error;
  71. if (formatA != formatB)
  72. {
  73. error.m_errorMessage = "Images format mismatch.";
  74. return AZ::Failure(error);
  75. }
  76. if (formatA != AZ::RHI::Format::R8G8B8A8_UNORM || formatB != AZ::RHI::Format::R8G8B8A8_UNORM)
  77. {
  78. error.m_errorMessage = "Unsupported image format.";
  79. return AZ::Failure(error);
  80. }
  81. if (sizeA != sizeB)
  82. {
  83. error.m_errorMessage = "Images size mismatch.";
  84. return AZ::Failure(error);
  85. }
  86. uint32_t totalPixelCount = sizeA.m_width * sizeA.m_height;
  87. if (bufferA.size() != bufferB.size()
  88. || bufferA.size() != BytesPerPixel * totalPixelCount
  89. || bufferB.size() != BytesPerPixel * totalPixelCount)
  90. {
  91. error.m_errorMessage = "Images size mismatch.";
  92. return AZ::Failure(error);
  93. }
  94. result.m_diffScore = 0.0f;
  95. result.m_filteredDiffScore = 0.0f;
  96. for (size_t i = 0; i < bufferA.size(); i += BytesPerPixel)
  97. {
  98. const float finalDiffNormalized = aznumeric_cast<float>(CalcMaxChannelDifference(bufferA, bufferB, i)) / 255.0f;
  99. const float squared = finalDiffNormalized * finalDiffNormalized;
  100. result.m_diffScore += squared;
  101. if (finalDiffNormalized > minDiffFilter)
  102. {
  103. result.m_filteredDiffScore += squared;
  104. }
  105. }
  106. result.m_diffScore = sqrt(result.m_diffScore / totalPixelCount);
  107. result.m_filteredDiffScore = sqrt(result.m_filteredDiffScore / totalPixelCount);
  108. return AZ::Success(result);
  109. }
  110. }
  111. }