Download

Having trouble loading a texture into SImage

I can’t figure out how to get an image loaded into a SImage widget. I Googled most of the afternoon and didn’t find anything that worked. There’s not many examples out there it seems.

What I’m trying to do is dynamically add a child Simage to a SHorizontalBox slot. The image is going where it’s supposed to go, but I keep getting the default grey checkerboard instead of the desired texture. What’s the magic trick behind this?

You’ve probably noticed that SImage uses a Slate Brush and not a plain UTexture2D or some similar asset. You might also have noticed that it is being provided and stored by pointer, i.e.: it has to exist somewhere else first. The simplest solution would be to store the FSlateBrush in the widget that owns the SImage. For instance, our inventory item widget accepts a UTexture2D* argument, saves it in a FSlateBrush and constructs its child SImage by passing a pointer to this FSlateBrush:


**Declaration**
class SInventoryItem : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS( SInventoryItem )
		: _ItemImage( nullptr )
	{}
		/** Item being displayed by this widget. */
		SLATE_ARGUMENT( UTexture2D*, ItemImage )

	SLATE_END_ARGS()
		
	/** Constructs this widget. */
	void Construct( const FArguments& InArgs );
	
protected:
	FSlateBrush ItemBrush;
}



**Construction**
void SInventoryItem::Construct( const FArguments& InArgs )
{
	ItemIconBrush.SetResourceObject( InArgs._ItemImage );
	ItemIconBrush.ImageSize.X = InArgs._ItemImage->GetSurfaceWidth();
	ItemIconBrush.ImageSize.Y = InArgs._ItemImage->GetSurfaceHeight();
	ItemIconBrush.DrawAs = ESlateBrushDrawType::Image;
		
	ChildSlot
	
		SNew( SImage )
		.Image( &ItemIconBrush )
	];
}


If you’re being stumped by the actual image loading part, this is a bit complex. Since Slate does not use UObject management, it doesn’t gather and load assets the same way. In the case of the above inventory item, the image texture is part of the item blueprint and loaded with it, with UI code extracting it to pass it to SInventoryItem.

For UI-centric images, the usual approach is to use Slate styles. There’s lots of different ways to use Slate styles, you can see some examples of these in ShooterGame and editor code.

Lastly, another option is to use FSlateDynamicImageBrush, which can load images by path instead of being constructed from an asset. Again, there’s a few examples of those both in ShooterGame (the loading screen) and editor code.

-Camille

I got it to work by creating a custom style. BTW, the widget I was trying to get to work was derived from UWidget.

I have a similar question
Can I load an image without creating a style?
I’m trying to set an BorderImage, but without creating a style I get white images.

My slate and function



SNew(SBorder)
     .HAlign(HAlign_Center)
     .VAlign(VAlign_Center)
     .BorderImage(this, &SMyUIWidget::GetSecondImageBrush)
     
         SNew(STextBlock)
         .Font(FSlateFontInfo("Veranda", 54))
         .ColorAndOpacity(FLinearColor(1, 1, 1, 1))
         .Text(FText::FromString("Page Two"))
     ]


const FSlateBrush* SMyUIWidget::GetSecondImageBrush() const
 {
     FString PathToImage;
 
     (SecondTabActive == 0) ? PathToImage = TEXT("Slate/tab_normal.png") : PathToImage = TEXT("Slate/tab_active.png");
 
     FString ImagePath = FPaths::GameContentDir() / PathToImage;
     FName BrushName = FName(*ImagePath);
 
     return new FSlateImageBrush(BrushName, FVector2D(256, 64));
 }

35943-scr.png

A few things:

  1. You are leaking memory. Slate does not delete brushes as it is assumed you are managing them yourself. So returning a new allocation like this will result in new brushes being created each time the function is called (i.e.: probably multiple times per frame!). At least store the brushes in your UI widget class during Construct, then have your function return a pointer to that.

  2. I’m not entirely sure loading a PNG file in this fashion will work properly in an actual cooked game. It’s not different from how C++ styles (not style assets) are loaded for the editor, but I haven’t seen any such usages in game samples. I’d venture this functionality was only meant for editor images. In any event, make sure Slate’s D3D logging (assuming you’re on Windows) is active (it should be, by default LogSlateD3D has Log verbosity). FSlateD3DTextureManager::LoadTexture should spew out warnings if the image fails to load.

  3. Whether it’s loading or not, a plain Slate reference to a PNG will not get packaged. Lacking the asset system functionality, it is invisible to the cooker and won’t be included. Be sure to add your Slate path to the project’s non-asset directories to package. It can be found in the project settings, under packaging, “Additional Non-Asset Directories to Package”.

I think your best bet would be to use a Slate dynamic brush. It’s a middle ground between a fully fledged style asset and a plain non-asset PNG image. You’ll have to import the image as a texture, then feed the path to that texture to a FSlateDynamicImageBrush. You then subclass the brush and load the image manually with LoadObject (look in ShooterGameLoadingScreen.cpp for an example).

Note that this dynamic image brush also will not be gathered for cooking, so you will need to manually load a reference somewhere in your project, or add the directory to your additional asset directories (same settings location as non-asset directories). Don’t mix and match non-asset and asset files in these manual directories, it tends to wreak havoc on cooking and packaging.

It’s not a very straightforward process, there’s a good reason style assets exist. :slight_smile:

-Camille

Thanks for the reply Camille, corrected work with style, I’m new to C ++.
But then tried using FSlateDynamicImageBrush. Came out shorter :slight_smile:

with style:




#define IMAGE_BRUSH(RelativePath, ...)	FSlateImageBrush(Style->RootToContentDir(RelativePath, TEXT(".png")), __VA_ARGS__)
 
TSharedPtr<ISlateStyle> CreateStyle()
{
	TSharedPtr<FSlateStyleSet> Style = MakeShareable(new FSlateStyleSet("PreloadStyle"));
	Style->SetContentRoot(FPaths::GameContentDir() / "Slate");
 
	Style->Set("tab_normal", new IMAGE_BRUSH("tab_normal", FVector2D(256, 64)));
	Style->Set("tab_active", new IMAGE_BRUSH("tab_active", FVector2D(256, 64)));
 
	return Style;
}
 
#undef IMAGE_BRUSH
 
 
TSharedPtr<ISlateStyle> FMyStyle::StylePtr = NULL;
 
void FMyStyle::Initialize()
{
	if (!StylePtr.IsValid())
	{
		StylePtr = CreateStyle();
		FSlateStyleRegistry::RegisterSlateStyle(*StylePtr);
	}
}
 
void FMyStyle::Shutdown()
{
	FSlateStyleRegistry::UnRegisterSlateStyle(*StylePtr);
	ensure(StylePtr.IsUnique());
	StylePtr.Reset();
}
 
const ISlateStyle& FMyStyle::Get()
{
	return *StylePtr;	
}





const FSlateBrush* SMyUIWidget::GetFirstImageBrush() const
{
	FName BrushName;
	(FirstTabActive == 0) ? BrushName = TEXT("tab_normal") : BrushName = TEXT("tab_active");
 
	return FMyStyle::Get().GetBrush(BrushName);
}




SNew(SBorder)
	.HAlign(HAlign_Center)
	.VAlign(VAlign_Center)
	.BorderImage(this, &SMyUIWidget::GetFirstImageBrush)
	
        ...
        ]


with FSlateDynamicImageBrush



UTexture2D* tabActiveImage;
UTexture2D* tabPassiveImage;

FSlateDynamicImageBrush* tab_active;
FSlateDynamicImageBrush* tab_normal;
...
AMyHUD::AMyHUD(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	ConstructorHelpers::FObjectFinder<UTexture2D> activeImage(TEXT("Texture2D'/Game/Slate/tab_active.tab_active'"));
	tabActiveImage = activeImage.Object;

	ConstructorHelpers::FObjectFinder<UTexture2D> passiveImage(TEXT("Texture2D'/Game/Slate/tab_normal.tab_normal'"));
	tabPassiveImage = passiveImage.Object;

	tab_active = new FSlateDynamicImageBrush(tabActiveImage, FVector2D(256, 64), FName("tabActiveImage"));
	tab_normal = new FSlateDynamicImageBrush(tabPassiveImage, FVector2D(256, 64), FName("tabPassiveImage"));
}
...
const FSlateBrush* SMyUIWidget::GetFirstImageBrush() const
{
	if (FirstTabActive == 0)
	{
		return tab_normal;
	}
	else
	{
		return tab_active;
	}
}
...
	SNew(SBorder)
		.HAlign(HAlign_Center)
		.VAlign(VAlign_Center)
		.BorderImage(this, &SMyUIWidget::GetFirstImageBrush)
		
                ...
		]