Realizar diversas operaciones de imágenes (UTexture2D) en UE (Unreal Engine) (leer, guardar, copiar, portapapeles...).
Las siguientes líneas de código están basadas en la versión 5.3 de UE.
Código fuente
Se pueden encontrar más detalles del código fuente en la tienda de UE mediante el complemento: AIChatPlus
Traducción al español:
Leer: UE implementa la lectura de imágenes del sistema local como UTexture2D
Método genérico
Este método es viable tanto en el modo editor como en el modo de juego, admite los formatos de archivo de imagen PNG, JPEG, BMP, ICO, EXR, ICNS, HDR, TIFF, DDS, TGA, y puede manejar la mayoría de los tipos de imágenes comunes.
El código también es muy limpio:
#include <Engine/Texture2D.h>
#include <ImageUtils.h>
UTexture2D* LoadImage(const FString& InLoadPath)
{
FImage ImageInfo;
FImageUtils::LoadImage(*InLoadPath, ImageInfo);
return FImageUtils::CreateTexture2DFromImage(ImageInfo);
}
La salida es UTexture2D.
Método exclusivo del editor
Esta metodología puede ofrecer soporte adicional para más tipos de imágenes: texturas UDIM, archivos IES, PCX, PSD.
La implementación del código será un poco más complicada:
#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
La implementación se realizó utilizando la función FactoryCreateBinary de UTextureFactory, la cual puede leer el tipo de archivo adicional mencionado anteriormente.
Copia: UE realiza la copia de UTexture2D
A veces es necesario hacer una copia de un UTexture2D y luego modificar esta imagen duplicada. Para copiar la imagen, es necesario utilizar la función incorporada del motor FImageCore::CopyImage
. Solo hay que configurar los parámetros de ambas imágenes y llamar a esta interfaz.
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;
}
Guardar: Realizar la acción de guardar un UTexture2D en un archivo
El punto clave es utilizar la función del motor FImageUtils::SaveImageAutoFormat
, es relativamente sencillo de implementar, pero es importante tener en cuenta los casos de reintento en caso de fallo
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);
}
}
Traduce este texto al español:
Guardar: La UE logra guardar UTexture2D como Asset.
Guardar el UTexture2D en la memoria en un Asset, y poder verlo en el explorador de recursos (Content Browser).
Traduce este texto al idioma español:
Las funciones principales requieren el uso de CopyTexture2D
implementada anteriormente. Primero necesitamos copiar una nueva imagen y luego llamar a UPackage::SavePackage
para guardar el Package
donde se encuentra la imagen como un activo.
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);
}
}
Portapapeles: Copia de imágenes (UTexture2D) a Windows Clipboard implementada por UE
Funciones relacionadas con Windows
Vamos a utilizar las funciones relacionadas con el portapapeles de Windows siguientes:
- OpenClipboardAbra el portapapeles y obtenga el controlador del portapapeles.
- EmptyClipboardPor favor, vacíe el portapapeles y asígnele la propiedad del mismo a la ventana actual.
- SetClipboardDataEstablecer los datos del portapapeles, los datos de la imagen se envían al portapapeles a través de esta interfaz.
- CloseClipboardUna vez establecidos los datos, cierre el portapapeles.
Formato de imagen en el portapapeles
Traduce este texto al idioma español:
Formato estándar del portapapelesSe describen los formatos disponibles en el portapapeles, siendo CF_DIBV5
uno de ellos que se utiliza para establecer imágenes.
CF_DIBV5 especifica el formato requerido definido concretamente estructura BITMAPV5HEADEREn este lugar, optamos por la siguiente configuración
Configuración de UTexture2D
Hemos seleccionado el espacio de color de la imagen del portapapeles como LCS_sRGB
, es decir, el espacio de color sRGB, por lo tanto, UTexture2D también debe configurarse primero en el formato correspondiente:
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 es responsable de convertir UTexture2D a un formato estándar: TC_VectorDisplacementmap (RGBA8) y espacio de color SRGB. Una vez alineados los formatos de imagen de UTexture2D y el portapapeles de Windows, podemos copiar los datos de la imagen al portapapeles.
Código específico
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;
}
Traducción de UTexture2D a Base64
Este proyecto es bastante sencillo de implementar, así que vamos directo al código.
#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;
}
Original: https://wiki.disenone.site/en
This post is protected by CC BY-NC-SA 4.0 agreement, should be reproduced with attribution.
Visitors. Total Visits. Page Visits.
Este post está traducido usando ChatGPT, por favor feedback si hay alguna omisión.