UE 实现各种图片 (UTexture2D) 操作 (读取、保存、复制、剪贴板...)
以下代码都是以 UE5.3 版本为例。
源码
更多源码细节可在 UE 商城获取插件:AIChatPlus
读取:UE 实现读取本地系统图片为 UTexture2D
通用方法
本方法在 编辑器 和 GamePlay 模式下都可行,支持的图片文件格式有 PNG, JPEG, BMP, ICO, EXR, ICNS, HDR, TIFF, DDS, TGA,基本能覆盖大部分的常用图片类型。
代码也很简洁:
#include <Engine/Texture2D.h>
#include <ImageUtils.h>
UTexture2D* LoadImage(const FString& InLoadPath)
{
FImage ImageInfo;
FImageUtils::LoadImage(*InLoadPath, ImageInfo);
return FImageUtils::CreateTexture2DFromImage(ImageInfo);
}
返回的即是 UTexture2D。
编辑器专用方法
本方法可以额外支持更多的图片类型:UDIM 纹理贴图、IES 文件、PCX 、PSD。
代码实现上会更复杂一些:
#include <Engine/Texture2D.h>
#include <Misc/FileHelper.h>
#include <Misc/Paths.h>
#include <UObject/UObjectGlobals.h>
#if WITH_EDITOR
UTexture2D* LoadImage(const FString& InLoadPath)
{
TArray64<uint8> Buffer;
if (!FFileHelper::LoadFileToArray(Buffer, *InLoadPath))
{
return nullptr;
}
const FString TextureName;
const FString Extension = FPaths::GetExtension(InLoadPath).ToLower();
const uint8* BufferPtr = Buffer.GetData();
auto TextureFact = NewObject<UTextureFactory>();
UTexture2D* Ret = Cast<UTexture2D>(TextureFact->FactoryCreateBinary(
UTexture2D::StaticClass(), GetTransientPackage(), *TextureName, RF_Transient,
NULL, *Extension, BufferPtr, BufferPtr + Buffer.Num(), GWarn));
return Ret;
}
#endif
实现是使用了 UTextureFactory 的 FactoryCreateBinary 函数,这个函数能够读取前面提到的额外文件类型。
复制:UE 实现复制 UTexture2D
有的时候需要复制一个 UTexture2D 出来,再对这个复制出来的图片进行修改,复制图片需要使用到引擎自带的函数 FImageCore::CopyImage
,只要设置好两个图片的参数,调用这个接口即可
UTexture2D* CopyTexture2D(UTexture2D* InTexture, UObject* Outer, FName Name, EObjectFlags Flags)
{
// src texture info, src ImageView
FTextureMipDataLockGuard InTextureGuard(InTexture);
uint8* SrcMipData = InTextureGuard.Lock(LOCK_READ_ONLY); // Texture->GetPlatformData()->Mips[0].BulkData.Lock(InLockFlag)
const int32 InSizeX = InTexture->GetSizeX();
const int32 InSizeY = InTexture->GetSizeY();
const EPixelFormat InFormat = InTexture->GetPixelFormat();
const FImageView SrcMipImage(
SrcMipData, InSizeX, InSizeY, 1, GetRawImageFormat(InFormat), InTexture->GetGammaSpace());
// create dst texture
UTexture2D* NewTexture = NewObject<UTexture2D>(Outer, Name, Flags);
NewTexture->SetPlatformData(new FTexturePlatformData());
NewTexture->GetPlatformData()->SizeX = InSizeX;
NewTexture->GetPlatformData()->SizeY = InSizeY;
NewTexture->GetPlatformData()->SetNumSlices(1);
NewTexture->GetPlatformData()->PixelFormat = InFormat;
// Allocate first mipmap.
int32 NumBlocksX = InSizeX / GPixelFormats[InFormat].BlockSizeX;
int32 NumBlocksY = InSizeY / GPixelFormats[InFormat].BlockSizeY;
FTexture2DMipMap* Mip = new FTexture2DMipMap();
Mip->SizeX = InSizeX;
Mip->SizeY = InSizeY;
Mip->SizeX = 1;
NewTexture->GetPlatformData()->Mips.Add(Mip);
Mip->BulkData.Lock(LOCK_READ_WRITE);
Mip->BulkData.Realloc((int64)NumBlocksX * NumBlocksY * GPixelFormats[InFormat].BlockBytes);
Mip->BulkData.Unlock();
// dst texture ImageView
uint8* DstMipData = static_cast<uint8*>(NewTexture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE));
const FImageView DstMipImage(
DstMipData, InSizeX, InSizeY, 1, GetRawImageFormat(InFormat), InTexture->GetGammaSpace());
// run CopyImage
FImageCore::CopyImage(SrcMipImage,DstMipImage);
#if WITH_EDITORONLY_DATA
NewTexture->Source.Init(
InSizeX, InSizeY, 1, 1,
FImageCoreUtils::ConvertToTextureSourceFormat(GetRawImageFormat(InFormat)), DstMipData);
#endif
// cleanup
NewTexture->GetPlatformData()->Mips[0].BulkData.Unlock();
NewTexture->UpdateResource();
return NewTexture;
}
保存:UE 实现保存 UTexture2D 到文件
核心是使用引擎函数 FImageUtils::SaveImageAutoFormat
,实现起来比较简单,不过需要注意失败重试的情况
void SaveImage(UTexture2D* InImage, const FString& InSavePath)
{
if (!InImage) return;
FImage ImageInfo;
if (FImageUtils::GetTexture2DSourceImage(InImage, ImageInfo))
{
FImageUtils::SaveImageAutoFormat(*InSavePath, ImageInfo);
}
else
{
// if prev save failed
// use ConvertTextureToStandard to change InImage to Standard format, and try again
// then revert InImage's origin format
// this is what FTextureMipDataLockGuard does
FTextureMipDataLockGuard InImageGuard(InImage);
uint8* MipData = InImageGuard.Lock(LOCK_READ_ONLY);
check( MipData != nullptr );
const FImageView MipImage(
MipData, InImage->GetSizeX(), InImage->GetSizeY(), 1,
GetRawImageFormat(InImage->GetPixelFormat()), InImage->GetGammaSpace());
FImageUtils::SaveImageAutoFormat(*InSavePath, MipImage);
}
}
保存:UE 实现保存 UTexture2D 到 Asset
保存内存中的 UTexture2D 到 Asset 中,并可以在资源浏览器 (Content Browser) 中查看。
核心函数需要用到上面实现的 CopyTexture2D
,我们需要先复制出来一个新的图片,然后再调用 UPackage::SavePackage
把图片所在的 Package
保存成 Asset。
void SaveTextureToAsset(UTexture2D* InTexture)
{
if (!InTexture) return;
// open save asset dialog, choose where/which to save
FSaveAssetDialogConfig SaveAssetDialogConfig;
SaveAssetDialogConfig.DefaultPath = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::NEW_ASSET);
SaveAssetDialogConfig.AssetClassNames.Add(UTexture2D::StaticClass()->GetClassPathName());
SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::AllowButWarn;
SaveAssetDialogConfig.DialogTitleOverride = FAIChatPlusEditor_Constants::FCText::SaveAsAsset;
const FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
const FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig);
if (SaveObjectPath.IsEmpty()) return;
// init save info
const FString PackageName = FPackageName::ObjectPathToPackageName(SaveObjectPath);
const FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension());
const FString PackagePath = FPaths::GetPath(PackageFileName);
const FString TextureName = FPaths::GetBaseFilename(PackageName);
// create new UPackage to put the new texture in
UPackage* const NewPackage = CreatePackage(*PackageName);
NewPackage->FullyLoad();
// copy texture
UTexture2D* NewTexture = UAIChatPlus_Util::CopyTexture2D(
InTexture, NewPackage, FName(TextureName), RF_Public | RF_Standalone | RF_Transactional);
// Generate the thumbnail
// if not doing so, the texture will not have thumbnail in content browser
FObjectThumbnail NewThumbnail;
ThumbnailTools::RenderThumbnail(
NewTexture, NewTexture->GetSizeX(), NewTexture->GetSizeY(),
ThumbnailTools::EThumbnailTextureFlushMode::NeverFlush, NULL,
&NewThumbnail);
ThumbnailTools::CacheThumbnail(NewTexture->GetFullName(), &NewThumbnail, NewPackage);
// setting up new package and new texture
NewPackage->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(NewTexture);
FEditorDirectories::Get().SetLastDirectory(ELastDirectory::NEW_ASSET, FPaths::GetPath(PackageName));
// save args
FSavePackageArgs SaveArgs;
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
SaveArgs.bForceByteSwapping = true;
SaveArgs.bWarnOfLongFilename = true;
// save it
if (!UPackage::SavePackage(NewPackage, NewTexture, *PackageFileName, SaveArgs))
{
UE_LOG(AIChatPlusEditor, Error, TEXT("Failed to save Asset: [%s]\n"), *PackageFileName);
}
}
剪贴板:UE 实现复制图片 (UTexture2D) 到 Windows 剪贴板 (Clipboard)
Windows 相关函数
我们会用到以下 Windows 操作剪贴板的相关函数:
- OpenClipboard:打开剪贴板,获取剪贴板的 Handler 。
- EmptyClipboard:清空剪贴板,并把剪贴板的所有权分配给当前的窗口。
- SetClipboardData:设置剪贴板的数据,图片的数据是通过这个接口发送给剪贴板。
- CloseClipboard:设置好数据之后,关闭剪贴板。
剪贴板的图片格式
标准剪贴板格式 里面介绍了可用的剪贴板格式,其中 CF_DIBV5
即可以用来设置图片。
CF_DIBV5 要求的格式具体定义 BITMAPV5HEADER 结构,在这里我们选用以下配置
UTexture2D 设置
我们在上面选择了剪贴板图片的颜色空间是 LCS_sRGB
,即 sRGB 颜色空间,那么 UTexture2D 也需要先设置到对应的格式:
bool ConvertTextureToStandard(UTexture2D* InTexture)
{
if (InTexture->CompressionSettings != TC_VectorDisplacementmap)
{
InTexture->CompressionSettings = TC_VectorDisplacementmap;
IsChanged = true;
}
if (InTexture->SRGB != true)
{
InTexture->SRGB = true;
IsChanged = true;
}
if (IsChanged)
{
InTexture->UpdateResource();
}
}
ConvertTextureToStandard 就是负责把 UTexture2D 转换成标准格式:TC_VectorDisplacementmap (RGBA8) 和 SRGB 颜色空间。对齐了 UTexture2D 和 Windows 剪贴板的图片格式之后,我们就可以把图片的数据复制到剪贴板上。
具体代码
void CopyTexture2DToClipboard(UTexture2D* InTexture)
{
if (!InTexture) return;
FTextureMipDataLockGuard InTextureGuard(InTexture);
// get InTexture info
uint8* SrcMipData = InTextureGuard.Lock(LOCK_READ_ONLY);
const int32 InSizeX = InTexture->GetSizeX();
const int32 InSizeY = InTexture->GetSizeY();
const EPixelFormat InFormat = InTexture->GetPixelFormat();
const FImageView SrcMipImage(
SrcMipData, InSizeX, InSizeY, 1, GetRawImageFormat(InTexture), InTexture->GetGammaSpace());
// set clipboard Texture info
const EPixelFormat OutFormat = PF_B8G8R8A8;
const int32 NumBlocksX = InSizeX / GPixelFormats[OutFormat].BlockSizeX;
const int32 NumBlocksY = InSizeY / GPixelFormats[OutFormat].BlockSizeY;
const int64 BufSize = static_cast<int64>(NumBlocksX) * NumBlocksY * GPixelFormats[InFormat].BlockBytes;
// set header info
BITMAPV5HEADER Header;
Header.bV5Size = sizeof(BITMAPV5HEADER);
Header.bV5Width = InSizeX;
Header.bV5Height = -InSizeY;
Header.bV5Planes = 1;
Header.bV5BitCount = 32;
Header.bV5Compression = BI_BITFIELDS;
Header.bV5SizeImage = BufSize;
Header.bV5XPelsPerMeter = 0;
Header.bV5YPelsPerMeter = 0;
Header.bV5ClrUsed = 0;
Header.bV5ClrImportant = 0;
Header.bV5RedMask = 0x00FF0000;
Header.bV5GreenMask = 0x0000FF00;
Header.bV5BlueMask = 0x000000FF;
Header.bV5AlphaMask = 0xFF000000;
Header.bV5CSType = LCS_sRGB;
// Header.bV5Endpoints; // ignored
Header.bV5GammaRed = 0;
Header.bV5GammaGreen = 0;
Header.bV5GammaBlue = 0;
Header.bV5Intent = 0;
Header.bV5ProfileData = 0;
Header.bV5ProfileSize = 0;
Header.bV5Reserved = 0;
HGLOBAL WinBuf = GlobalAlloc(GMEM_MOVEABLE, sizeof(BITMAPV5HEADER) + BufSize);
if (WinBuf == NULL)
return;
HWND WinHandler = GetActiveWindow();
if (!OpenClipboard(WinHandler)) {
GlobalFree(WinBuf);
return;
}
verify(EmptyClipboard());
// copy InTexture into BGRA8 sRGB Standard Texture
FTexture2DMipMap* DstMip = new FTexture2DMipMap();
DstMip->SizeX = InSizeX;
DstMip->SizeY = InSizeY;
DstMip->SizeZ = 1;
DstMip->BulkData.Lock(LOCK_READ_WRITE);
uint8* DstMipData = static_cast<uint8*>(DstMip->BulkData.Realloc(BufSize));
const FImageView DstMipImage(
DstMipData, InSizeX, InSizeY, 1, ERawImageFormat::BGRA8, EGammaSpace::sRGB);
FImageCore::CopyImage(SrcMipImage,DstMipImage);
DstMip->BulkData.Unlock();
// copy Standard Texture data into Clipboard
void * WinLockedBuf = GlobalLock(WinBuf);
if (WinLockedBuf) {
memcpy(WinLockedBuf, &Header, sizeof(BITMAPV5HEADER));
memcpy((char*)WinLockedBuf + sizeof(BITMAPV5HEADER), DstMipData, BufSize);
}
GlobalUnlock(WinLockedBuf);
if (!SetClipboardData(CF_DIBV5, WinBuf))
{
UE_LOG(AIChatPlus_Internal, Fatal, TEXT("SetClipboardData failed with error code %i"), (uint32)GetLastError() );
}
// finish, close clipboard
verify(CloseClipboard());
delete DstMip;
}
UTexture2D 跟 Base64 之间的转换
这个实现起来比较简单,直接上代码
#include <Misc/Base64.h>
#include <ImageUtils.h>
UTexture2D* B64ToImage(const FString& B64)
{
TArray<uint8> Data;
FBase64::Decode(B64, Data);
return FImageUtils::ImportBufferAsTexture2D(Data);
}
FString ImageToB64(UTexture2D* InTexture, const int32 InQuality)
{
FTextureMipDataLockGuard InTextureGuard(InTexture);
uint8* MipData = InTextureGuard.Lock(LOCK_READ_ONLY);
check(MipData != nullptr);
const FImageView InImage(
MipData, InTexture->GetSizeX(), InTexture->GetSizeY(), 1,
GetRawImageFormat(InTexture->GetPixelFormat()), InTexture->GetGammaSpace());
TArray64<uint8> Buffer;
FString Ret;
if (FImageUtils::CompressImage(Buffer, TEXT("png"), InImage, InQuality))
{
Ret = FBase64::Encode(Buffer.GetData(), Buffer.Num());
}
return Ret;
}
原文地址:https://wiki.disenone.site
本篇文章受 CC BY-NC-SA 4.0 协议保护,转载请注明出处。
Visitors. Total Visits. Page Visits.