// Copyright Epic Games, Inc. All Rights Reserved. #include "AssetUtils/Texture2DUtil.h" #include "EngineModule.h" #include "RendererInterface.h" #include "RenderUtils.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "ExplicitUseGeometryMathTypes.h" // using UE::Geometry::(math types) using namespace UE::Geometry; static bool ReadTexture_PlatformData( UTexture2D* TextureMap, FImageDimensions& Dimensions, TImageBuilder& DestImage) { // Read from PlatformData // UBlueprintMaterialTextureNodesBPLibrary::Texture2D_SampleUV_EditorOnly() shows how to read from PlatformData // without converting formats if it's already uncompressed. And can read PF_FloatRGBA. Would make sense to do // that when possible, and perhaps is possible to convert to PF_FloatRGBA instead of using TC_VectorDisplacementmap? // // Note that the current code cannot run on a background thread, UpdateResource() will call FlushRenderingCommands() // which will check() if it's on the Game Thread check(TextureMap->PlatformData); int32 Width = TextureMap->PlatformData->Mips[0].SizeX; int32 Height = TextureMap->PlatformData->Mips[0].SizeY; Dimensions = FImageDimensions(Width, Height); DestImage.SetDimensions(Dimensions); int64 Num = Dimensions.Num(); // convert built platform texture data to uncompressed RGBA8 format TextureCompressionSettings InitialCompressionSettings = TextureMap->CompressionSettings; bool bWasSRGB = TextureMap->SRGB; #if WITH_EDITOR TextureMipGenSettings InitialMipGenSettings = TextureMap->MipGenSettings; #endif TextureMap->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap; TextureMap->SRGB = false; #if WITH_EDITOR TextureMap->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; #endif TextureMap->UpdateResource(); // lock texture and represet as FColor const FColor* FormattedImageData = reinterpret_cast(TextureMap->PlatformData->Mips[0].BulkData.LockReadOnly()); // maybe could be done more quickly by row? for (int32 i = 0; i < Num; ++i) { FColor ByteColor = FormattedImageData[i]; DestImage.SetPixel(i, FVector4f(ByteColor.ReinterpretAsLinear())); } // restore built platform texture data to initial state TextureMap->PlatformData->Mips[0].BulkData.Unlock(); TextureMap->CompressionSettings = InitialCompressionSettings; TextureMap->SRGB = bWasSRGB; #if WITH_EDITOR TextureMap->MipGenSettings = InitialMipGenSettings; #endif TextureMap->UpdateResource(); return true; } #if WITH_EDITOR static bool ReadTexture_SourceData( UTexture2D* TextureMap, FImageDimensions& Dimensions, TImageBuilder& DestImage) { FTextureSource& TextureSource = TextureMap->Source; int32 Width = TextureSource.GetSizeX(); int32 Height = TextureSource.GetSizeY(); Dimensions = FImageDimensions(Width, Height); DestImage.SetDimensions(Dimensions); int64 Num = Dimensions.Num(); TArray64 SourceData; TextureMap->Source.GetMipData(SourceData, 0, 0, 0); ETextureSourceFormat SourceFormat = TextureSource.GetFormat(); int32 BytesPerPixel = TextureSource.GetBytesPerPixel(); const uint8* SourceDataPtr = SourceData.GetData(); // code below is derived from UBlueprintMaterialTextureNodesBPLibrary::Texture2D_SampleUV_EditorOnly() if ((SourceFormat == TSF_BGRA8 || SourceFormat == TSF_BGRE8)) { check(BytesPerPixel == sizeof(FColor)); for (int32 i = 0; i < Num; ++i) { const uint8* PixelPtr = SourceDataPtr + (i * BytesPerPixel); FColor PixelColor = *((FColor*)PixelPtr); FLinearColor FloatColor = (TextureMap->SRGB) ? FLinearColor::FromSRGBColor(PixelColor) : PixelColor.ReinterpretAsLinear(); DestImage.SetPixel(i, FVector4f(FloatColor)); } } else if ((SourceFormat == TSF_RGBA16 || SourceFormat == TSF_RGBA16F)) { check(BytesPerPixel == sizeof(FFloat16Color)); for (int32 i = 0; i < Num; ++i) { const uint8* PixelPtr = SourceDataPtr + (i * BytesPerPixel); DestImage.SetPixel(i, FVector4f( ((const FFloat16Color*)PixelPtr)->GetFloats() )); } } else if (SourceFormat == TSF_G8) { check(BytesPerPixel == 1); for (int32 i = 0; i < Num; ++i) { const uint8* PixelPtr = SourceDataPtr + (i * BytesPerPixel); uint8 PixelColor = *PixelPtr; float PixelColorf = float(PixelColor) / 255.0f; FLinearColor FloatColor = (TextureMap->SRGB) ? FLinearColor::FromSRGBColor(FColor(PixelColor, PixelColor, PixelColor, 255)) : FLinearColor(PixelColorf, PixelColorf, PixelColorf, 1.0); DestImage.SetPixel(i, FVector4f(FloatColor)); } } return true; } #endif bool UE::AssetUtils::ReadTexture( UTexture2D* TextureMap, FImageDimensions& Dimensions, TImageBuilder& DestImage, bool bPreferPlatformData) { if (ensure(TextureMap) == false) return false; #if WITH_EDITOR bool bHasVTData = TextureMap->GetPlatformData()->VTData != nullptr; bool bHasMips = TextureMap->GetPlatformData()->Mips.Num() != 0; ensure(bHasVTData != bHasMips); // should be one or the other if (TextureMap->Source.IsValid() && (bPreferPlatformData == false || bHasMips == false)) { return ReadTexture_SourceData(TextureMap, Dimensions, DestImage); } #endif return ReadTexture_PlatformData(TextureMap, Dimensions, DestImage); } bool UE::AssetUtils::ConvertToSingleChannel(UTexture2D* TextureMap) { if (ensure(TextureMap) == false) return false; #if WITH_EDITOR bool bHasVTData = TextureMap->GetPlatformData()->VTData != nullptr; bool bHasMips = TextureMap->GetPlatformData()->Mips.Num() != 0; ensure(bHasVTData != bHasMips); // should be one or the other if (ensure(TextureMap->Source.IsValid()) && bHasVTData == false) { FTextureSource& TextureSource = TextureMap->Source; int32 Width = TextureSource.GetSizeX(); int32 Height = TextureSource.GetSizeY(); int64 Num = Width * Height; ETextureSourceFormat SourceFormat = TextureSource.GetFormat(); if (SourceFormat == TSF_G8) { return true; // already single channel } if (SourceFormat != TSF_BGRA8 && SourceFormat != TSF_BGRE8) { ensureMsgf(false, TEXT("ConvertToSingleChannel currently only supports RGBA8 textures")); return false; } TArray64 NewSourceData; NewSourceData.SetNum(Width * Height); TArray64 SourceData; TextureMap->Source.GetMipData(SourceData, 0, 0, 0); int32 BytesPerPixel = TextureSource.GetBytesPerPixel(); check(BytesPerPixel == sizeof(FColor)); const uint8* SourceDataPtr = SourceData.GetData(); for (int32 i = 0; i < Num; ++i) { const uint8* PixelPtr = SourceDataPtr + (i * BytesPerPixel); FColor PixelColor = *((FColor*)PixelPtr); NewSourceData[i] = PixelColor.R; } TextureSource.Init(Width, Height, 1, 1, TSF_G8, &NewSourceData[0]); TextureMap->UpdateResource(); return true; } return false; #else ensureMsgf(false, TEXT("ConvertToSingleChannel currently requires editor-only SourceData")); return false; #endif } bool UE::AssetUtils::ForceVirtualTexturePrefetch(FImageDimensions ScreenSpaceDimensions, bool bWaitForPrefetchToComplete) { // Prefetch all virtual textures so that we have content available if (UseVirtualTexturing(GMaxRHIFeatureLevel)) { const FVector2D ScreenSpaceSize(ScreenSpaceDimensions.GetWidth(), ScreenSpaceDimensions.GetHeight()); ENQUEUE_RENDER_COMMAND(AssetUtils_ForceVirtualTexturePrefetch)( [ScreenSpaceSize](FRHICommandListImmediate& RHICmdList) { GetRendererModule().RequestVirtualTextureTiles(ScreenSpaceSize, -1); GetRendererModule().LoadPendingVirtualTextureTiles(RHICmdList, GMaxRHIFeatureLevel); }); if (bWaitForPrefetchToComplete) { FlushRenderingCommands(); } return true; } return false; } bool UE::AssetUtils::SaveDebugImage( const TArray& Pixels, FImageDimensions Dimensions, FString DebugSubfolder, FString FilenameBase, int32 UseFileCounter) { static int32 CaptureIndex = 0; #if WITH_EDITOR // Save capture result to a file to ease debugging TRACE_CPUPROFILER_EVENT_SCOPE(AssetUtils_SaveDebugImage); FString DirectoryPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()); if (DebugSubfolder.Len() > 0) { DirectoryPath = FPaths::Combine(DirectoryPath, DebugSubfolder); } int32 FileCounter = (UseFileCounter > 0) ? UseFileCounter : CaptureIndex++; FString Filename = FString::Printf(TEXT("%s-%04d.bmp"), *FilenameBase, FileCounter); FString FilePath = FPaths::Combine(DirectoryPath, Filename); return FFileHelper::CreateBitmap(*FilePath, Dimensions.GetWidth(), Dimensions.GetHeight(), Pixels.GetData()); #else return false; #endif } bool UE::AssetUtils::SaveDebugImage( const TArray& Pixels, FImageDimensions Dimensions, bool bConvertToSRGB, FString DebugSubfolder, FString FilenameBase, int32 UseFileCounter ) { static int32 CaptureIndex = 0; #if WITH_EDITOR // Save capture result to a file to ease debugging TRACE_CPUPROFILER_EVENT_SCOPE(AssetUtils_SaveDebugImage); FString DirectoryPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()); if (DebugSubfolder.Len() > 0) { DirectoryPath = FPaths::Combine(DirectoryPath, DebugSubfolder); } int32 FileCounter = (UseFileCounter > 0) ? UseFileCounter : CaptureIndex++; FString Filename = FString::Printf(TEXT("%s-%04d.bmp"), *FilenameBase, FileCounter); FString FilePath = FPaths::Combine(DirectoryPath, Filename); TArray ConvertedColor; ConvertedColor.Reserve(Pixels.Num()); for (const FLinearColor& LinearColor : Pixels) { ConvertedColor.Add(LinearColor.ToFColor(bConvertToSRGB)); } return FFileHelper::CreateBitmap(*FilePath, Dimensions.GetWidth(), Dimensions.GetHeight(), ConvertedColor.GetData()); #else return false; #endif } bool UE::AssetUtils::SaveDebugImage( const FImageAdapter& Image, bool bConvertToSRGB, FString DebugSubfolder, FString FilenameBase, int32 UseFileCounter) { static int32 CaptureIndex = 0; #if WITH_EDITOR // Save capture result to a file to ease debugging TRACE_CPUPROFILER_EVENT_SCOPE(AssetUtils_SaveDebugImage); FString DirectoryPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir()); if (DebugSubfolder.Len() > 0) { DirectoryPath = FPaths::Combine(DirectoryPath, DebugSubfolder); } int32 FileCounter = (UseFileCounter > 0) ? UseFileCounter : CaptureIndex++; FString Filename = FString::Printf(TEXT("%s-%04d.bmp"), *FilenameBase, FileCounter); FString FilePath = FPaths::Combine(DirectoryPath, Filename); FImageDimensions Dimensions = Image.GetDimensions(); int64 N = Dimensions.Num(); TArray ConvertedColor; ConvertedColor.Reserve(N); for ( int64 i = 0; i < N; ++i ) { FLinearColor LinearColor = (FLinearColor)Image.GetPixel(i); ConvertedColor.Add(LinearColor.ToFColor(bConvertToSRGB)); } return FFileHelper::CreateBitmap(*FilePath, Dimensions.GetWidth(), Dimensions.GetHeight(), ConvertedColor.GetData()); #else return false; #endif }