diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h index 7c35a0634e..dcd95c7fb6 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/ImageProcessingDefines.h @@ -49,20 +49,6 @@ namespace ImageProcessingAtom static const unsigned int s_MinReduceLevel = 0; static const unsigned int s_MaxReduceLevel = 5; - static const int s_TotalSupportedImageExtensions = 10; - static const char* s_SupportedImageExtensions[s_TotalSupportedImageExtensions] = { - "*.tif", - "*.tiff", - "*.png", - "*.bmp", - "*.jpg", - "*.jpeg", - "*.tga", - "*.gif", - "*.dds", - "*.exr" - }; - enum class RGBWeight : AZ::u32 { uniform, // uniform weights (1.0, 1.0, 1.0) (default) diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp index 55a6c1ea8f..8c2e61ff6d 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp @@ -27,6 +27,7 @@ #include #include +#include #include namespace ImageProcessingAtom @@ -66,9 +67,9 @@ namespace ImageProcessingAtom AssetBuilderSDK::AssetBuilderDesc builderDescriptor; builderDescriptor.m_name = "Atom Image Builder"; - for (int i = 0; i < s_TotalSupportedImageExtensions; i++) + for (int i = 0; i < AZ::RPI::s_TotalSupportedImageExtensions; i++) { - builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(s_SupportedImageExtensions[i], AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); + builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZ::RPI::s_SupportedImageExtensions[i], AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); } builderDescriptor.m_busId = azrtti_typeid(); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp index 665d08b3ae..f0d881c416 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessingSystemComponent.cpp @@ -7,6 +7,7 @@ */ +#include #include #include @@ -223,9 +224,9 @@ namespace ImageProcessingAtom { AZStd::string targetExtension = entry->GetExtension(); - for (int i = 0; i < s_TotalSupportedImageExtensions; i++) + for (int i = 0; i < AZ::RPI::s_TotalSupportedImageExtensions; i++) { - if (AZStd::wildcard_match(s_SupportedImageExtensions[i], targetExtension.c_str())) + if (AZStd::wildcard_match(AZ::RPI::s_SupportedImageExtensions[i], targetExtension.c_str())) { return true; } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h index 185666626c..66c91e92cf 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/Image.h @@ -22,6 +22,20 @@ namespace AZ { class ImageAsset; + static const int s_TotalSupportedImageExtensions = 10; + static const char* s_SupportedImageExtensions[s_TotalSupportedImageExtensions] = { + "*.tif", + "*.tiff", + "*.png", + "*.bmp", + "*.jpg", + "*.jpeg", + "*.tga", + "*.gif", + "*.dds", + "*.exr" + }; + //! A base class for images providing access to common image information. class Image : public Data::InstanceData diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp index e13db253f3..9ff26f0d3a 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp @@ -133,7 +133,8 @@ namespace AZ case AZ::RHI::Format::R16G16_UNORM: case AZ::RHI::Format::R16G16B16A16_UNORM: { - return mem[index] / static_cast(std::numeric_limits::max()); + auto actualMem = reinterpret_cast(mem); + return actualMem[index] / static_cast(std::numeric_limits::max()); } case AZ::RHI::Format::R16_SNORM: case AZ::RHI::Format::R16G16_SNORM: @@ -480,14 +481,13 @@ namespace AZ const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); auto width = imageDescriptor.m_size.m_width; const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); - const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format) / numComponents; size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * pixelSize + componentIndex; + size_t imageDataIndex = (y * width + x) * numComponents + componentIndex; auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveFloatValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); @@ -513,14 +513,13 @@ namespace AZ const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); auto width = imageDescriptor.m_size.m_width; const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); - const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format) / numComponents; size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * pixelSize + componentIndex; + size_t imageDataIndex = (y * width + x) * numComponents + componentIndex; auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveUintValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); @@ -546,14 +545,13 @@ namespace AZ const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor(); auto width = imageDescriptor.m_size.m_width; const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format); - const uint32_t pixelSize = AZ::RHI::GetFormatSize(imageDescriptor.m_format) / numComponents; size_t outValuesIndex = 0; for (uint32_t y = topLeft.second; y < bottomRight.second; ++y) { for (uint32_t x = topLeft.first; x < bottomRight.first; ++x) { - size_t imageDataIndex = (y * width + x) * pixelSize + componentIndex; + size_t imageDataIndex = (y * width + x) * numComponents + componentIndex; auto& outValue = outValues[outValuesIndex++]; outValue = Internal::RetrieveIntValue(imageData.data(), imageDataIndex, imageDescriptor.m_format); diff --git a/Gems/GradientSignal/Code/CMakeLists.txt b/Gems/GradientSignal/Code/CMakeLists.txt index d2d8f8c696..febf7d9cd4 100644 --- a/Gems/GradientSignal/Code/CMakeLists.txt +++ b/Gems/GradientSignal/Code/CMakeLists.txt @@ -17,9 +17,12 @@ ly_add_target( PUBLIC Include BUILD_DEPENDENCIES + PRIVATE + AZ::AtomCore PUBLIC AZ::AzCore AZ::AzFramework + Gem::Atom_RPI.Public Gem::SurfaceData Gem::ImageProcessingAtom.Headers Gem::LmbrCentral diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h index 044e9d91b1..0cbd8bd4c7 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ImageGradientComponent.h @@ -8,8 +8,11 @@ #pragma once +#include #include #include +#include +#include #include #include #include @@ -25,6 +28,19 @@ namespace LmbrCentral namespace GradientSignal { + // Custom JSON serializer for ImageGradientConfig to handle version conversion + class JsonImageGradientConfigSerializer + : public AZ::BaseJsonSerializer + { + public: + AZ_RTTI(GradientSignal::JsonImageGradientConfigSerializer, "{C5B982C8-2E81-45C3-8932-B6F54B28F493}", AZ::BaseJsonSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + AZ::JsonSerializationResult::Result Load( + void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + AZ::JsonDeserializerContext& context) override; + }; + class ImageGradientConfig : public AZ::ComponentConfig { @@ -32,7 +48,7 @@ namespace GradientSignal AZ_CLASS_ALLOCATOR(ImageGradientConfig, AZ::SystemAllocator, 0); AZ_RTTI(ImageGradientConfig, "{1BDB5DA4-A4A8-452B-BE6D-6BD451D4E7CD}", AZ::ComponentConfig); static void Reflect(AZ::ReflectContext* context); - AZ::Data::Asset m_imageAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; + AZ::Data::Asset m_imageAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; float m_tilingX = 1.0f; float m_tilingY = 1.0f; }; diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h index 4ffab0b4b4..811d74082d 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/ImageAsset.h @@ -12,6 +12,7 @@ #include #include #include +#include namespace AZ { @@ -61,6 +62,6 @@ namespace GradientSignal } }; - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); + float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue); } // namespace GradientSignal diff --git a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp index 3639d5c6df..e3f128ec95 100644 --- a/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/ImageGradientComponent.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -18,13 +19,100 @@ namespace GradientSignal { + AZ::JsonSerializationResult::Result JsonImageGradientConfigSerializer::Load( + void* outputValue, [[maybe_unused]] const AZ::Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, AZ::JsonDeserializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + auto configInstance = reinterpret_cast(outputValue); + AZ_Assert(configInstance, "Output value for JsonImageGradientConfigSerializer can't be null."); + + JSR::ResultCode result(JSR::Tasks::ReadField); + + rapidjson::Value::ConstMemberIterator itr = inputValue.FindMember("ImageAsset"); + if (itr != inputValue.MemberEnd()) + { + // Version 1 stored a custom GradientSignal::ImageAsset as the image asset. + // In Version 2, we changed the image asset to use the generic AZ::RPI::StreamingImageAsset, + // so they are both AZ::Data::Asset but reference different types. + // Using the assetHint, which will be something like "my_test_image.gradimage", + // we need to find the valid streaming image asset product from the same source, + // which will be something like "my_test_image.png.streamingimage" + AZStd::string assetHint; + AZ::Data::AssetId fixedAssetId; + auto it = itr->value.FindMember("assetHint"); + if (it != itr->value.MemberEnd()) + { + AZ::ScopedContextPath subPath(context, "assetHint"); + result.Combine(ContinueLoading(&assetHint, azrtti_typeid(), it->value, context)); + + if (assetHint.ends_with(".gradimage")) + { + // We don't know what image format the original source was, so we need to loop through + // all the supported image extensions to check if they have a valid corresponding + // streaming image asset + for (int i = 0; i < AZ::RPI::s_TotalSupportedImageExtensions; i++) + { + AZStd::string imageExtension(AZ::RPI::s_SupportedImageExtensions[i]); + + // The image extensions are stored with a wildcard (e.g. *.png) so we need to strip that off first + AZ::StringFunc::Replace(imageExtension, "*", ""); + + // Form potential streaming image path (e.g. my_test_image.png.streamingimage) + AZStd::string potentialStreamingImagePath(assetHint); + AZ::StringFunc::Replace(potentialStreamingImagePath, ".gradimage", ""); + potentialStreamingImagePath += imageExtension + ".streamingimage"; + + // Check if there is a valid streaming image asset for this path + AZ::Data::AssetCatalogRequestBus::BroadcastResult(fixedAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, potentialStreamingImagePath.c_str(), azrtti_typeid>(), false); + if (fixedAssetId.IsValid()) + { + break; + } + } + } + } + + // Replace the old gradimage with new AssetId for streaming image asset (if we needed to replace) + if (fixedAssetId.IsValid()) + { + configInstance->m_imageAsset = AZ::Data::AssetManager::Instance().GetAsset(fixedAssetId, AZ::Data::AssetLoadBehavior::QueueLoad); + } + // Otherwise, this was already a streaming image asset, so just load it like normal + else + { + result.Combine(ContinueLoadingFromJsonObjectField( + &configInstance->m_imageAsset, azrtti_typeidm_imageAsset)>(), inputValue, "ImageAsset", context)); + } + } + + result.Combine(ContinueLoadingFromJsonObjectField( + &configInstance->m_tilingX, azrtti_typeidm_tilingX)>(), inputValue, "TilingX", context)); + + result.Combine(ContinueLoadingFromJsonObjectField( + &configInstance->m_tilingY, azrtti_typeidm_tilingY)>(), inputValue, "TilingY", context)); + + return context.Report(result, + result.GetProcessing() != JSR::Processing::Halted ? + "Successfully loaded ImageGradientConfig information." : + "Failed to load ImageGradientConfig information."); + } + + AZ_CLASS_ALLOCATOR_IMPL(JsonImageGradientConfigSerializer, AZ::SystemAllocator, 0); + void ImageGradientConfig::Reflect(AZ::ReflectContext* context) { + if (auto jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + AZ::SerializeContext* serialize = azrtti_cast(context); if (serialize) { serialize->Class() - ->Version(1) + ->Version(2) ->Field("ImageAsset", &ImageGradientConfig::m_imageAsset) ->Field("TilingX", &ImageGradientConfig::m_tilingX) ->Field("TilingY", &ImageGradientConfig::m_tilingY) @@ -265,7 +353,7 @@ namespace GradientSignal { AZStd::unique_lock imageLock(m_imageMutex); - m_configuration.m_imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, azrtti_typeid(), m_configuration.m_imageAsset.GetAutoLoadBehavior()); + m_configuration.m_imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, azrtti_typeid(), m_configuration.m_imageAsset.GetAutoLoadBehavior()); } SetupDependencies(); diff --git a/Gems/GradientSignal/Code/Source/ImageAsset.cpp b/Gems/GradientSignal/Code/Source/ImageAsset.cpp index 7e5667a5be..6ccd9e0a2a 100644 --- a/Gems/GradientSignal/Code/Source/ImageAsset.cpp +++ b/Gems/GradientSignal/Code/Source/ImageAsset.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -152,17 +153,15 @@ namespace GradientSignal return true; } - float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue) + float GetValueFromImageAsset(const AZ::Data::Asset& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue) { if (imageAsset.IsReady()) { - const auto& image = imageAsset.Get(); - AZStd::size_t imageSize = image->m_imageWidth * image->m_imageHeight * - static_cast(image->m_bytesPerPixel); - - if (image->m_imageWidth > 0 && - image->m_imageHeight > 0 && - image->m_imageData.size() == imageSize) + auto imageDescriptor = imageAsset->GetImageDescriptor(); + auto width = imageDescriptor.m_size.m_width; + auto height = imageDescriptor.m_size.m_height; + + if (width > 0 && height > 0) { // When "rasterizing" from uvs, a range of 0-1 has slightly different meanings depending on the sampler state. // For repeating states (Unbounded/None, Repeat), a uv value of 1 should wrap around back to our 0th pixel. @@ -185,8 +184,8 @@ namespace GradientSignal // A 16x16 pixel image and tilingX = tilingY = 1 maps the uv range of 0-1 to 0-16 pixels. // A 16x16 pixel image and tilingX = tilingY = 1.5 maps the uv range of 0-1 to 0-24 pixels. - const AZ::Vector3 tiledDimensions((image->m_imageWidth * tilingX), - (image->m_imageHeight * tilingY), + const AZ::Vector3 tiledDimensions((width * tilingX), + (height * tilingY), 0.0f); // Convert from uv space back to pixel space @@ -195,13 +194,13 @@ namespace GradientSignal // UVs outside the 0-1 range are treated as infinitely tiling, so that we behave the same as the // other gradient generators. As mentioned above, if clamping is desired, we expect it to be applied // outside of this function. - size_t x = static_cast(pixelLookup.GetX()) % image->m_imageWidth; - size_t y = static_cast(pixelLookup.GetY()) % image->m_imageHeight; + auto x = aznumeric_cast(pixelLookup.GetX()) % width; + auto y = aznumeric_cast(pixelLookup.GetY()) % height; // Flip the y because images are stored in reverse of our world axes - size_t index = ((image->m_imageHeight - 1) - y) * image->m_imageWidth + x; + y = (height - 1) - y; - return RetrieveValue(image->m_imageData.data(), index, image->m_imageFormat); + return AZ::RPI::GetSubImagePixelValue(imageAsset, x, y); } }