You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/GradientSignal/Code/Source/Editor/EditorImageBuilderComponent...

308 lines
14 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorImageBuilderComponent.h"
#include <AssetBuilderSDK/SerializationDependencies.h>
#include <GradientSignal/ImageAsset.h>
#include <GradientSignal/ImageSettings.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/Debug/Trace.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <QImageReader>
#include <QDirIterator>
#include <GradientSignalSystemComponent.h>
#include <GradientSignal/GradientImageConversion.h>
#include <Atom/ImageProcessing/ImageObject.h>
#include <Atom/ImageProcessing/ImageProcessingBus.h>
namespace GradientSignal
{
EditorImageBuilderPluginComponent::EditorImageBuilderPluginComponent()
{
// AZ Components should only initialize their members to null and empty in constructor
// after construction, they may be deserialized from file.
}
EditorImageBuilderPluginComponent::~EditorImageBuilderPluginComponent()
{
}
void EditorImageBuilderPluginComponent::Init()
{
}
void EditorImageBuilderPluginComponent::Activate()
{
// Activate is where you'd perform registration with other objects and systems.
// Since we want to register our builder, we do that here:
AssetBuilderSDK::AssetBuilderDesc builderDescriptor;
builderDescriptor.m_name = "Gradient Image Builder";
builderDescriptor.m_version = 1;
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.tif", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.tiff", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.png", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.bmp", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.jpg", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.jpeg", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.tga", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.gif", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.bt", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
builderDescriptor.m_busId = EditorImageBuilderWorker::GetUUID();
builderDescriptor.m_createJobFunction = AZStd::bind(&EditorImageBuilderWorker::CreateJobs, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
builderDescriptor.m_processJobFunction = AZStd::bind(&EditorImageBuilderWorker::ProcessJob, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
m_imageBuilder.BusConnect(builderDescriptor.m_busId);
EBUS_EVENT(AssetBuilderSDK::AssetBuilderBus, RegisterBuilderInformation, builderDescriptor);
}
void EditorImageBuilderPluginComponent::Deactivate()
{
m_imageBuilder.BusDisconnect();
}
void EditorImageBuilderPluginComponent::Reflect(AZ::ReflectContext* context)
{
ImageSettings::Reflect(context);
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<EditorImageBuilderPluginComponent, AZ::Component>()->Version(0)
->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }));
}
}
EditorImageBuilderWorker::EditorImageBuilderWorker()
{
}
EditorImageBuilderWorker::~EditorImageBuilderWorker()
{
}
void EditorImageBuilderWorker::ShutDown()
{
// it is important to note that this will be called on a different thread than your process job thread
m_isShuttingDown = true;
}
// this happens early on in the file scanning pass
// this function should consistently always create the same jobs, and should do no checking whether the job is up to date or not - just be consistent.
void EditorImageBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
{
AZStd::string fullPath;
AzFramework::StringFunc::Path::Join(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true, true);
// Check file for _GSI suffix/pattern which assumes processing will occur whether or not settings are provided
AZStd::string patternPath = fullPath;
AZStd::to_upper(patternPath.begin(), patternPath.end());
bool patternMatched = patternPath.rfind("_GSI.") != AZStd::string::npos;
// Determine if a settings file has been provided
AZStd::string settingsPath = fullPath + "." + s_gradientImageSettingsExtension;
bool settingsExist = AZ::IO::SystemFile::Exists(settingsPath.data());
// If the settings file is modified the image must be reprocessed
AssetBuilderSDK::SourceFileDependency sourceFileDependency;
sourceFileDependency.m_sourceFileDependencyPath = settingsPath;
response.m_sourceFileDependencyList.push_back(sourceFileDependency);
// If no settings file was provided then skip the file, unless the file name matches the convenience pattern
if (!patternMatched && !settingsExist)
{
//do nothing if settings aren't provided
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
AZStd::unique_ptr<ImageSettings> settingsPtr;
if (settingsExist)
{
settingsPtr.reset(AZ::Utils::LoadObjectFromFile<ImageSettings>(settingsPath));
}
// If the settings file didn't load then skip the file, unless the file name matches the convenience pattern
if (!patternMatched && !settingsPtr)
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Failed to create gradient image conversion job for %s.\nFailed loading settings %s.\n", fullPath.data(), settingsPath.data());
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Failed;
return;
}
// If settings loaded but processing is disabled then skip the file
if (settingsPtr && !settingsPtr->m_shouldProcess)
{
//do nothing if settings disable processing
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
// Get the extension of the file
AZStd::string ext;
AzFramework::StringFunc::Path::GetExtension(request.m_sourceFile.data(), ext, false);
AZStd::to_upper(ext.begin(), ext.end());
// We process the same file for all platforms
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
{
AssetBuilderSDK::JobDescriptor descriptor;
descriptor.m_jobKey = ext + " Compile (Gradient Image)";
descriptor.SetPlatformIdentifier(info.m_identifier.data());
descriptor.m_critical = false;
response.m_createJobOutputs.push_back(descriptor);
}
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
// later on, this function will be called for jobs that actually need doing.
// the request will contain the CreateJobResponse you constructed earlier, including any keys and values you placed into the hash table
void EditorImageBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
{
// Before we begin, let's make sure we are not meant to abort.
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
if (jobCancelListener.IsCancelled())
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Cancelled gradient image conversion job for %s.\nCancellation requested.\n", request.m_fullPath.data());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
if (m_isShuttingDown)
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Cancelled gradient image conversion job for %s.\nShutdown requested.\n", request.m_fullPath.data());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
return;
}
// Do conversion and get exported file's path
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Performing gradient image conversion job for %s\n", request.m_fullPath.data());
auto imageAsset = LoadImageFromPath(request.m_fullPath);
if (!imageAsset)
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Failed gradient image conversion job for %s.\nFailed loading source image %s.\n", request.m_fullPath.data(), request.m_fullPath.data());
return;
}
auto imageSettings = LoadImageSettingsFromPath(request.m_fullPath);
if (!imageSettings)
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Failed gradient image conversion job for %s.\nFailed loading source image %s.\n", request.m_fullPath.data(), request.m_fullPath.data());
return;
}
// ChannelMask is 8 bits, and the bits are assumed to be as follows: 0b0000ABGR
imageAsset = ConvertImage(*imageAsset, *imageSettings);
//generate export file name
QDir dir(request.m_tempDirPath.data());
if (!dir.exists())
{
dir.mkpath(".");
}
AZStd::string fileName;
AZStd::string outputPath;
AzFramework::StringFunc::Path::GetFileName(request.m_fullPath.data(), fileName);
fileName += AZStd::string(".") + s_gradientImageExtension;
AzFramework::StringFunc::Path::Join(request.m_tempDirPath.data(), fileName.data(), outputPath, true, true);
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Output path for gradient image conversion: %s\n", outputPath.data());
//save asset
if (!AZ::Utils::SaveObjectToFile(outputPath, AZ::DataStream::ST_XML, imageAsset.get()))
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Failed gradient image conversion job for %s.\nFailed saving output file %s.\n", request.m_fullPath.data(), outputPath.data());
return;
}
// Report the image-import result
AssetBuilderSDK::JobProduct jobProduct;
if(!AssetBuilderSDK::OutputObject(imageAsset.get(), outputPath, azrtti_typeid<ImageAsset>(), 2, jobProduct))
{
AZ_Error(AssetBuilderSDK::ErrorWindow, false, "Failed to output product dependencies.");
return;
}
response.m_outputProducts.push_back(jobProduct);
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Completed gradient image conversion job for %s.\nSucceeded saving output file %s.\n", request.m_fullPath.data(), outputPath.data());
}
AZ::Uuid EditorImageBuilderWorker::GetUUID()
{
return AZ::Uuid::CreateString("{7520DF20-16CA-4CF6-A6DB-D96759A09EE4}");
}
static AZStd::unique_ptr<ImageAsset> AtomLoadImageFromPath(const AZStd::string& fullPath)
{
ImageProcessingAtom::IImageObjectPtr imageObject;
ImageProcessingAtom::ImageProcessingRequestBus::BroadcastResult(imageObject, &ImageProcessingAtom::ImageProcessingRequests::LoadImage,
fullPath);
if (!imageObject)
{
return {};
}
//create a new image asset
auto imageAsset = AZStd::make_unique<ImageAsset>();
if (!imageAsset)
{
return {};
}
imageAsset->m_imageWidth = imageObject->GetWidth(0);
imageAsset->m_imageHeight = imageObject->GetHeight(0);
imageAsset->m_imageFormat = imageObject->GetPixelFormat();
AZ::u8* mem = nullptr;
AZ::u32 pitch = 0;
AZ::u32 mipBufferSize = imageObject->GetMipBufSize(0);
imageObject->GetImagePointer(0, mem, pitch);
imageAsset->m_imageData = { mem, mem + mipBufferSize };
return imageAsset;
}
AZStd::unique_ptr<ImageAsset> EditorImageBuilderWorker::LoadImageFromPath(const AZStd::string& fullPath)
{
return AtomLoadImageFromPath(fullPath);
}
AZStd::unique_ptr<ImageSettings> EditorImageBuilderWorker::LoadImageSettingsFromPath(const AZStd::string& fullPath)
{
// Determine if a settings file has been provided
AZStd::string settingsPath = fullPath + "." + s_gradientImageSettingsExtension;
bool settingsExist = AZ::IO::SystemFile::Exists(settingsPath.data());
if (settingsExist)
{
return AZStd::unique_ptr<ImageSettings>{AZ::Utils::LoadObjectFromFile<ImageSettings>(settingsPath)};
}
else
{
return AZStd::make_unique<ImageSettings>();
}
}
} // namespace GradientSignal