Rama's Extra Blueprint Nodes for UE5, No C++ Required!

You Can Easily Create Save Game Images With Victory Plugin for UE5!

Save Scene Capture To PNG

T2D Render Format, I highly recommend RTF RGBA8 SRGB, as this will have the same output as the way your in-game camera looks! <3

Please note I attached the SceneCaptureComponent2D to the Camera of the Character, so it will see what the camera sees :slight_smile:

Also note I am using Final Color HDR to include Post Processing Effects.

Performance

I recommend enabling and disabling tick on the SceneCaptureComponent so that it is only active while you are processing the userโ€™s request to save the game. The advantage of this setup is simplicty, but for benefit of that simplicity you should be careful to manage the ticking of the SceneCaptureComponent2D for optimum performance :slight_smile:

Saving the Scene Capture as JPG to file!

Load PNG To T2D

Loading Image to UI!


C++ Code

.h

/** Load a Texture2D from a JPG,PNG,BMP,ICO,EXR,ICNS file! IsValid tells you if file path was valid or not. The image type is assumed from an extension such as .jpg, .png, .bmp. Enjoy! - */
	UFUNCTION(BlueprintCallable, Category = "Victory BP Library|Load Texture From File",meta=(Keywords="image png jpg jpeg bmp bitmap ico icon exr icns"))
	static UTexture2D* Victory_LoadTexture2D_FromFileByExtension(const FString& FullFilePath,bool& IsValid, int32& Width, int32& Height);

/** 
		I highly recommend that your Texture Render Target Format be "RTF RGB8 SRGB" both so it is compatible, and so it looks the same as in-game 
			<3 . 
			
		Make sure to include the appropriate image extension in your file path! Recommended: .bmp, .jpg, .png. Contributed by Community Member Kris! 
	*/
	UFUNCTION(Category = "Victory BP Library|SceneCapture", BlueprintCallable)
	static bool CaptureComponent2D_SaveImage(class USceneCaptureComponent2D* Target, const FString ImagePath, const FLinearColor ClearColour);	

.cpp

static TSharedPtr<IImageWrapper> GetImageWrapperByExtention(const FString InImagePath)
{
	//EndsWith is not case sensitive by default, see unrealstring.h btw -
	
    IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
    if (InImagePath.EndsWith(".png"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
    }
    else if (InImagePath.EndsWith(".jpg") || InImagePath.EndsWith(".jpeg"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG);
    }
    else if (InImagePath.EndsWith(".bmp"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::BMP);
    }
    else if (InImagePath.EndsWith(".ico"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::ICO);
    }
    else if (InImagePath.EndsWith(".exr"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::EXR);
    }
    else if (InImagePath.EndsWith(".icns"))
    {
        return ImageWrapperModule.CreateImageWrapper(EImageFormat::ICNS);
    }
    
    return nullptr;
}

static UTexture2D* LoadTexture2D(const FString& FullFilePath, TSharedPtr<IImageWrapper> ImageWrapper, bool& IsValid, int32& Width, int32& Height)
{
	IsValid = false;
	UTexture2D* LoadedT2D = NULL;
	
	
	//Load From File
	TArray<uint8> RawFileData;
	if (!FFileHelper::LoadFileToArray(RawFileData, *FullFilePath)) 
	{
		return NULL;
	}
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	  
	//Create T2D!
	if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num()))
	{ 
		TArray<uint8> UncompressedBGRA;
		if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA))
		{
			LoadedT2D = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8);
			
			//Valid?
			if(!LoadedT2D) 
			{
				return NULL;
			}
			//~~~~~~~~~~~~~~
			
			//Out!
			Width = ImageWrapper->GetWidth();
			Height = ImageWrapper->GetHeight();
			 
			//Copy!
			void* TextureData = LoadedT2D->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
			FMemory::Memcpy(TextureData, UncompressedBGRA.GetData(), UncompressedBGRA.Num());
			LoadedT2D->PlatformData->Mips[0].BulkData.Unlock();

			//Update!
			LoadedT2D->UpdateResource();
		}
	}
	  
	// Success!
	IsValid = true;
	return LoadedT2D;
}

UTexture2D* UVictoryBPFunctionLibrary::Victory_LoadTexture2D_FromFile(const FString& FullFilePath,EJoyImageFormats ImageFormat,bool& IsValid, int32& Width, int32& Height)
{
	IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
	TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(GetJoyImageFormat(ImageFormat));
	
	return LoadTexture2D(FullFilePath,ImageWrapper,IsValid,Width,Height);
}

UTexture2D* UVictoryBPFunctionLibrary::Victory_LoadTexture2D_FromFileByExtension(const FString& FullFilePath,bool& IsValid, int32& Width, int32& Height)
{
	TSharedPtr<IImageWrapper> ImageWrapper = GetImageWrapperByExtention(FullFilePath);
	
	return LoadTexture2D(FullFilePath,ImageWrapper,IsValid,Width,Height);
}

bool UVictoryBPFunctionLibrary::CaptureComponent2D_SaveImage(class USceneCaptureComponent2D* Target, const FString ImagePath, const FLinearColor ClearColour)
{ 
	// Bad scene capture component! No render target! Stay! Stay! Ok, feed!... wait, where was I?
	if ((Target == nullptr) || (Target->TextureTarget == nullptr))
	{
		#if WITH_EDITOR
			FMessageLog("PIE").Error(FText::Format(LOCTEXT("CaptureComponent2D_SaveImage", "UVictoryBPFunctionLibrary::CaptureComponent2D_SaveImage >> Please supply a Capture Component with a valid T2D Render Target :) <3  {0}'"), FText::FromString(ImagePath)));
		#endif // WITH_EDITOR
		
		return false;
	}
	
	FRenderTarget* RenderTarget = Target->TextureTarget->GameThread_GetRenderTargetResource();
	if (RenderTarget == nullptr)
	{
		return false;
	}

	TArray<FColor> RawPixels;
	
	// Format not supported - use PF_B8G8R8A8.
	if (Target->TextureTarget->GetFormat() != PF_B8G8R8A8)
	{
		#if WITH_EDITOR
			FMessageLog("PIE").Error(FText::Format(LOCTEXT("CaptureComponent2D_SaveImage", "UVictoryBPFunctionLibrary::CaptureComponent2D_SaveImage >> The Texture Render Target (the texture render asset itself) format is not supported - use PF_B8G8R8A8 <3  & Kris: {0}'"), FText::FromString(ImagePath)));
		#endif // WITH_EDITOR
		
		return false;
	}

	if (!RenderTarget->ReadPixels(RawPixels))
	{
		return false;
	}

	// Convert to FColor.
	FColor ClearFColour = ClearColour.ToFColor(false); // FIXME - want sRGB or not?

	for (auto& Pixel : RawPixels)
	{
		// Switch Red/Blue changes.
		const uint8 PR = Pixel.R;
		const uint8 PB = Pixel.B;
		Pixel.R = PB;
		Pixel.B = PR;

		// Set alpha based on RGB values of ClearColour.
		Pixel.A = ((Pixel.R == ClearFColour.R) && (Pixel.G == ClearFColour.G) && (Pixel.B == ClearFColour.B)) ? 0 : 255;
	}
	
	TSharedPtr<IImageWrapper> ImageWrapper = GetImageWrapperByExtention(ImagePath);

	const int32 Width = Target->TextureTarget->SizeX;
	const int32 Height = Target->TextureTarget->SizeY;
	
	if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(&RawPixels[0], RawPixels.Num() * sizeof(FColor), Width, Height, ERGBFormat::RGBA, 8))
	{
		return FFileHelper::SaveArrayToFile(ImageWrapper->GetCompressed(), *ImagePath);
	}
	
	return false;
}

Additional Pics

Here are some additional pics I took, notice they have square dimensions, as those were the settings in the Texture Render Target (2048x2048). You can change the Texture Render Target dimensions to whatever you like :slight_smile:

:heart:

PS: @vladislav42 Thanks for the request! Enjoooy!

PSS: Download link is in original post!

1 Like