Aller au contenu

La réalisation de diverses opérations sur les images (UTexture2D) dans l'UE (lecture, enregistrement, copie, presse-papiers...).

Le code suivant est basé sur la version UE5.3.

Code source

Pour plus de détails sur le code source, vous pouvez obtenir le plugin sur l'UE Marketplace : AIChatPlus

Lecture : UE permet de lire les images du système local en UTexture2D.

Méthode générale

Cette méthode est applicable à la fois en mode éditeur et en mode de jeu, prenant en charge les formats de fichiers image suivants : PNG, JPEG, BMP, ICO, EXR, ICNS, HDR, TIFF, DDS, TGA, couvrant ainsi la plupart des types d'images couramment utilisés.

Le code est également très propre :

#include <Engine/Texture2D.h>
#include <ImageUtils.h>

UTexture2D* LoadImage(const FString& InLoadPath)
{
    FImage ImageInfo;
    FImageUtils::LoadImage(*InLoadPath, ImageInfo);
    return FImageUtils::CreateTexture2DFromImage(ImageInfo);
}

Le retour est un UTexture2D.

Méthodes spéciales pour l'éditeur

Cette méthode peut également prendre en charge davantage de types d'images : textures UDIM, fichiers IES, PCX, PSD.

La mise en œuvre du code sera un peu plus complexe :

#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 réalisation utilise la fonction FactoryCreateBinary de UTextureFactory, qui permet de lire les types de fichiers supplémentaires mentionnés précédemment.

Copie : UE réalise la copie UTexture2D

Parfois, il est nécessaire de dupliquer un UTexture2D, puis de modifier cette copie d'image. La duplication de l'image nécessite l'utilisation de la fonction intégrée du moteur FImageCore::CopyImage. Il suffit de configurer les paramètres des deux images et d'appeler cette interface.

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;
}

Sauvegarde : Réalisation de la sauvegarde d'un UTexture2D dans un fichier

Le cœur du fonctionnement repose sur la fonction moteur FImageUtils::SaveImageAutoFormat. Sa mise en œuvre est relativement simple, mais il est important de prêter attention aux situations d'échec et de réessayer.

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);
    }
}

Sauvegarde : UE accomplir la sauvegarde de UTexture2D en tant qu'Asset

Sauvegardez UTexture2D dans la mémoire en tant qu'Asset et visualisez-le dans le navigateur de contenu (Content Browser).

La fonction principale nécessite d'utiliser CopyTexture2D implémenté ci-dessus. Nous devons d'abord créer une nouvelle image, puis appeler UPackage::SavePackage pour enregistrer le Package de l'image en tant qu'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);
    }
}

Presse-papiers : Copie d'une image (UTexture2D) vers le presse-papiers de Windows (Clipboard) par l'UE.

Fonctions liées à Windows

Nous allons utiliser les fonctions suivantes liées au presse-papiers de Windows :

  • OpenClipboard: Ouvrez le presse-papiers et obtenez le gestionnaire du presse-papiers.
  • EmptyClipboardVider le presse-papiers et attribuer la propriété du presse-papiers à la fenêtre actuelle.
  • SetClipboardData:Définir les données du presse-papiers, les données d'image sont envoyées au presse-papiers via cette interface.
  • CloseClipboardAprès avoir configuré les données, fermez le presse-papiers.

Format d'image du presse-papiers

Format de presse-papiers standardLe document présente les formats de presse-papiers disponibles, parmi lesquels CF_DIBV5 peut être utilisé pour définir une image.

CF_DIBV5 format requis définition spécifique STRUCTURE BITMAPV5HEADERNous avons choisi la configuration suivante.

BITMAPV5HEADER Header;
Header.bV5CSType        = LCS_sRGB;
Header.bV5Compression   = BI_BITFIELDS;

UTexture2D paramètres

Nous avons choisi l'espace colorimétrique de l'image du presse-papiers comme LCS_sRGB, c'est-à-dire l'espace colorimétrique sRGB, donc UTexture2D doit également être configuré au format correspondant :

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 est responsable de la conversion d'UTexture2D en format standard : TC_VectorDisplacementmap (RGBA8) et espace de couleur SRGB. Après avoir aligné UTexture2D avec le format d'image du presse-papiers Windows, nous pouvons copier les données de l'image dans le presse-papiers.

Code spécifique

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;
}

Conversion entre UTexture2D et Base64

Cette réalisation est relativement simple, je vais directement vous montrer le code.

#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/fr

This post is protected by CC BY-NC-SA 4.0 agreement, should be reproduced with attribution.

Ce message a été traduit à l'aide de ChatGPT, veuillez laisser vos commentairesVeuillez indiquer toute omission.