Files
UnrealEngine/Engine/Source/Editor/Blutility/Private/AsyncImageExport.cpp
2025-05-18 13:04:45 +08:00

183 lines
5.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AsyncImageExport.h"
#include "Async/Async.h"
#include "Async/TaskGraphInterfaces.h"
#include "Containers/ContainersFwd.h"
#include "Engine/Texture.h"
#include "Engine/TextureDefines.h"
#include "Engine/TextureRenderTarget2D.h"
#include "IImageWrapper.h"
#include "ImagePixelData.h"
#include "ImageWrapperHelper.h"
#include "ImageWriteQueue.h"
#include "ImageWriteTask.h"
#include "Math/Color.h"
#include "Math/IntPoint.h"
#include "Math/IntRect.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "RHI.h"
#include "RHICommandList.h"
#include "RenderingThread.h"
#include "Templates/Casts.h"
#include "Templates/Function.h"
#include "Templates/UniquePtr.h"
#include "Templates/UnrealTemplate.h"
#include "TextureResource.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
//----------------------------------------------------------------------//
// UAsyncImageExport
//----------------------------------------------------------------------//
UAsyncImageExport::UAsyncImageExport()
{
}
UAsyncImageExport* UAsyncImageExport::ExportImageAsync(UTexture* Texture, const FString& OutputFile, int32 Quality)
{
UAsyncImageExport* AsyncTask = NewObject<UAsyncImageExport>();
AsyncTask->Quality = Quality;
AsyncTask->Start(Texture, OutputFile);
return AsyncTask;
}
void UAsyncImageExport::Start(UTexture* Texture, const FString& InOutputFile)
{
TextureToExport = Texture;
TargetFile = InOutputFile;
}
void UAsyncImageExport::Activate()
{
TWeakObjectPtr<UAsyncImageExport> WeakThis(this);
if (Cast<UTextureRenderTarget2D>(TextureToExport))
{
ENQUEUE_RENDER_COMMAND(ReadTextureCommand)(
[this, WeakThis](FRHICommandListImmediate& RHICmdList)
{
FTextureResource* Resource = TextureToExport->GetResource();
FRHITexture* ResourceRHI = Resource->GetTexture2DRHI();
TArray<FColor> OutPixels;
if (ensure(ResourceRHI))
{
OutPixels.SetNum(Resource->GetSizeX() * Resource->GetSizeY());
RHICmdList.ReadSurfaceData(
ResourceRHI,
FIntRect(0, 0, Resource->GetSizeX(), Resource->GetSizeY()),
OutPixels,
FReadSurfaceDataFlags()
);
}
FIntPoint ImageSize(Resource->GetSizeX(), Resource->GetSizeY());
AsyncTask(ENamedThreads::GameThread, [this, WeakThis, ImageSize, Pixels = MoveTemp(OutPixels)]() mutable {
if (WeakThis.IsValid())
{
ExportImage(MoveTemp(Pixels), ImageSize);
}
});
});
}
else
{
FTextureSource& TextureSource = TextureToExport->Source;
check( TextureSource.IsValid() );
// todo : should use GetMipImage instead of GetMipData ; use FImage and FImageUtils for image export
TArray64<uint8> TextureRawData;
verify( TextureSource.GetMipData(TextureRawData, 0) );
const int32 BytesPerPixel = static_cast<int32>(TextureSource.GetBytesPerPixel());
const ETextureSourceFormat SourceFormat = TextureSource.GetFormat();
const int32 Width = static_cast<int32>(TextureSource.GetSizeX());
const int32 Height = static_cast<int32>(TextureSource.GetSizeY());
TArray<FColor> OutPixels;
OutPixels.SetNumZeroed(Width * Height);
FColor Color(0, 0, 0, 0);
for (int32 Y = 0; Y < Height; ++Y)
{
for (int32 X = 0; X < Width; ++X)
{
const int32 PixelByteOffset = (X + Y * Width) * BytesPerPixel;
const uint8* PixelPtr = TextureRawData.GetData() + PixelByteOffset;
switch (SourceFormat)
{
case TSF_BGRA8:
case TSF_BGRE8:
{
Color = *((FColor*)PixelPtr);
break;
}
case TSF_G8:
{
const uint8 Intensity = *PixelPtr;
Color = FColor(Intensity, Intensity, Intensity, Intensity);
break;
}
default:
{
ensureMsgf(false, TEXT("Unknown Format"));
break;
}
}
OutPixels[Y * Width + X] = Color;
}
}
FIntPoint ImageSize(Width, Height);
AsyncTask(ENamedThreads::GameThread, [this, WeakThis, ImageSize, Pixels = MoveTemp(OutPixels)]() mutable {
if (WeakThis.IsValid())
{
ExportImage(MoveTemp(Pixels), ImageSize);
}
});
}
}
void UAsyncImageExport::ExportImage(TArray<FColor>&& RawPixels, FIntPoint ImageSize)
{
const FString AbsoluteFilePath = FPaths::IsRelative(TargetFile) ? FPaths::ConvertRelativePathToFull(TargetFile) : TargetFile;
IImageWriteQueueModule* ImageWriteQueueModule = FModuleManager::Get().GetModulePtr<IImageWriteQueueModule>("ImageWriteQueue");
FString Ext = FPaths::GetExtension(TargetFile);
EImageFormat ImageFormat = ImageWrapperHelper::GetImageFormat(Ext);
TUniquePtr<FImageWriteTask> ImageTask = MakeUnique<FImageWriteTask>();
ImageTask->Format = ImageFormat;
ImageTask->PixelData = MakeUnique<TImagePixelData<FColor>>(ImageSize, TArray64<FColor>(MoveTemp(RawPixels)));
ImageTask->Filename = AbsoluteFilePath;
ImageTask->bOverwriteFile = true;
ImageTask->CompressionQuality = Quality;
TWeakObjectPtr<UAsyncImageExport> WeakThis(this);
ImageTask->OnCompleted = [this, WeakThis](bool bSuccess) {
if (WeakThis.IsValid())
{
NotifyComplete(bSuccess);
}
};
ImageWriteQueueModule->GetWriteQueue().Enqueue(MoveTemp(ImageTask));
}
void UAsyncImageExport::NotifyComplete(bool bSuccess)
{
Complete.Broadcast(bSuccess);
SetReadyToDestroy();
}