Here is the solution I found to load targa files at runtime, using stb library.
First add the image functions to you project. It’s all in this c header file: https://github.com/nothings/stb/blob/master/stb_image.h
Then you can add these methods in your C++ class:
UTexture2D* UImageLoader::CreateTexture(UObject* Outer, const TArray<uint8>& PixelData, int32 InSizeX, int32 InSizeY, EPixelFormat InFormat, FName BaseName)
{
// Shamelessly copied from UTexture2D::CreateTransient with a few modifications
if (InSizeX <= 0 || InSizeY <= 0 ||
(InSizeX % GPixelFormats[InFormat].BlockSizeX) != 0 ||
(InSizeY % GPixelFormats[InFormat].BlockSizeY) != 0)
{
UIL_LOG(Warning, TEXT("Invalid parameters specified for UImageLoader::CreateTexture()"));
return nullptr;
}
// Most important difference with UTexture2D::CreateTransient: we provide the new texture with a name and an owner
FName TextureName = MakeUniqueObjectName(Outer, UTexture2D::StaticClass(), BaseName);
UTexture2D* NewTexture = NewObject<UTexture2D>(Outer, TextureName, RF_Transient);
NewTexture->PlatformData = new FTexturePlatformData();
NewTexture->PlatformData->SizeX = InSizeX;
NewTexture->PlatformData->SizeY = InSizeY;
NewTexture->PlatformData->PixelFormat = InFormat;
// Allocate first mipmap and upload the pixel data
int32 NumBlocksX = InSizeX / GPixelFormats[InFormat].BlockSizeX;
int32 NumBlocksY = InSizeY / GPixelFormats[InFormat].BlockSizeY;
FTexture2DMipMap* Mip = new(NewTexture->PlatformData->Mips) FTexture2DMipMap();
Mip->SizeX = InSizeX;
Mip->SizeY = InSizeY;
Mip->BulkData.Lock(LOCK_READ_WRITE);
void* TextureData = Mip->BulkData.Realloc(NumBlocksX * NumBlocksY * GPixelFormats[InFormat].BlockBytes);
FMemory::Memcpy(TextureData, PixelData.GetData(), PixelData.Num());
Mip->BulkData.Unlock();
NewTexture->UpdateResource();
return NewTexture;
}
UTexture2D* UImageLoader::LoadTGAImageFromDisk(UObject* Outer, const FString& ImagePath)
{
// Check if the file exists first
if (!FPaths::FileExists(ImagePath))
{
UIL_LOG(Error, TEXT("File not found: %s"), *ImagePath);
return nullptr;
}
// Load the compressed byte data from the file
TArray<uint8> FileData;
if (!FFileHelper::LoadFileToArray(FileData, *ImagePath))
{
UIL_LOG(Error, TEXT("Failed to load file: %s"), *ImagePath);
return nullptr;
}
int x,y,n;
unsigned char *data = stbi_load(TCHAR_TO_ANSI(*ImagePath), &x, &y, &n, 4);
if (data == nullptr)
UIL_LOG(Error, TEXT("Failed to load TGA data for file: %s"), *ImagePath);
TArray<uint8> RawData;
RawData.Append(data, x*y*GPixelFormats[PF_R8G8B8A8].BlockBytes);
stbi_image_free(data);
// Create the texture and upload the uncompressed image data
FString TextureBaseName = TEXT("Texture_") + FPaths::GetBaseFilename(ImagePath);
return CreateTexture(Outer, RawData, x, y, PF_R8G8B8A8, FName(*TextureBaseName));
}
To make it blueprint callable you add the following in your header :
UFUNCTION(BlueprintCallable, Category = ImageLoader, meta = (HidePin = "Outer", DefaultToSelf = "Outer"))
static UTexture2D* LoadTGAImageFromDisk(UObject* Outer, const FString& ImagePath);
Here are the includes and declaration needed:
#include "ModuleManager.h"
#include "FileHelper.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <vector>
// Change the UE_LOG log category name below to whichever log category you want to use.
#define UIL_LOG(Verbosity, Format, ...) UE_LOG(LogTemp, Verbosity, Format, __VA_ARGS__)
You also might have to add those dependency modules in your Build.cs file
PrivateDependencyModuleNames.AddRange(new string[] { "ImageWrapper", "RenderCore" });