// Icon Studio - Mathew Simon

#include "IconStudioRenderer.h"

#include "IconStudioPreset.h"

#include "Components/DirectionalLightComponent.h"
#include "Components/SceneCaptureComponent2D.h"
#include "Components/SkyLightComponent.h"
#include "Components/StaticMeshComponent.h"
#include "ContentStreaming.h"
#include "Engine/StaticMesh.h"
#include "Engine/Texture2D.h"
#include "Engine/TextureCube.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/World.h"
#include "Materials/MaterialInterface.h"
#include "PreviewScene.h"
#include "RenderingThread.h"
#include "TextureResource.h"

namespace IconStudioRendererLocal
{
	/** Un pixel de la scene de rendu, avant reduction. */
	struct FSample
	{
		FLinearColor Color = FLinearColor::Black;
		bool bCovered = false;
	};

	/**
	 * Compose la rotation d'une lumiere dans le repere de la camera.
	 *
	 * L'ordre compte : c'est la rotation camera qui porte l'offset, et non
	 * l'inverse. Ecrit dans l'autre sens, incliner la camera vers le bas ferait
	 * pivoter les lumieres autour d'un axe qui n'a rien a voir.
	 */
	FRotator ComposeLightRotation(const FRotator& CameraRotation, const FIconStudioLight& Light)
	{
		const FQuat Composed = CameraRotation.Quaternion() * FRotator(Light.Pitch, Light.Yaw, 0.f).Quaternion();
		return Composed.Rotator();
	}
}

FIconStudioRenderer::FIconStudioRenderer()
{
}

FIconStudioRenderer::~FIconStudioRenderer()
{
	// La scene detient ses composants : les detruire soi-meme laisserait des
	// proxies de rendu enregistres dans un monde qui n'existe plus.
	Scene.Reset();
}

void FIconStudioRenderer::AddReferencedObjects(FReferenceCollector& Collector)
{
	Collector.AddReferencedObject(MeshComponent);
	Collector.AddReferencedObject(CaptureComponent);
	Collector.AddReferencedObject(KeyLightComponent);
	Collector.AddReferencedObject(FillLightComponent);
	Collector.AddReferencedObject(RimLightComponent);
	Collector.AddReferencedObject(SkyLightComponent);
	Collector.AddReferencedObject(ColorTarget);
	Collector.AddReferencedObject(DepthTarget);
	Collector.AddReferencedObject(PreviewTexture);
	Collector.AddReferencedObject(AppliedSkyCubemap);
}

void FIconStudioRenderer::EnsureScene()
{
	if (Scene.IsValid())
	{
		return;
	}

	// Pas d'eclairage par defaut : le studio pose lui-meme ses trois lumieres,
	// et une directionnelle surnumeraire fausserait tous les presets. Pas de
	// scene physique non plus, rien ne tombe ici.
	FPreviewScene::ConstructionValues CVS;
	CVS.SetCreateDefaultLighting(false)
		.SetCreatePhysicsScene(false)
		.ShouldSimulatePhysics(false)
		.SetTransactional(false)
		.AllowAudioPlayback(false)
		.SetForceMipsResident(true);

	Scene = MakeUnique<FPreviewScene>(CVS);

	MeshComponent = NewObject<UStaticMeshComponent>(GetTransientPackage(), NAME_None, RF_Transient);
	MeshComponent->SetMobility(EComponentMobility::Movable);
	Scene->AddComponent(MeshComponent, FTransform::Identity);

	auto MakeLight = [this](TObjectPtr<UDirectionalLightComponent>& Out)
	{
		Out = NewObject<UDirectionalLightComponent>(GetTransientPackage(), NAME_None, RF_Transient);
		Out->SetMobility(EComponentMobility::Movable);
		// Une directionnelle en mouvement declenche des cascades d'ombres
		// dimensionnees pour un niveau entier ; ici l'objet tient dans quelques
		// dizaines de centimetres, d'ou une seule cascade tres courte.
		Out->DynamicShadowDistanceMovableLight = 2000.f;
		Out->DynamicShadowCascades = 1;
		Scene->AddComponent(Out, FTransform::Identity);
	};

	MakeLight(KeyLightComponent);
	MakeLight(FillLightComponent);
	MakeLight(RimLightComponent);

	SkyLightComponent = NewObject<USkyLightComponent>(GetTransientPackage(), NAME_None, RF_Transient);
	SkyLightComponent->SetMobility(EComponentMobility::Movable);
	SkyLightComponent->SourceType = SLS_SpecifiedCubemap;
	SkyLightComponent->bLowerHemisphereIsBlack = false;
	SkyLightComponent->CastShadows = false;
	SkyLightComponent->bRealTimeCapture = false;
	Scene->AddComponent(SkyLightComponent, FTransform::Identity);

	CaptureComponent = NewObject<USceneCaptureComponent2D>(GetTransientPackage(), NAME_None, RF_Transient);
	CaptureComponent->SetMobility(EComponentMobility::Movable);
	// La capture est declenchee a la main : sans monde tique, "chaque image" ne
	// veut rien dire, et on ne veut surtout pas d'un rendu par frame d'editeur.
	CaptureComponent->bCaptureEveryFrame = false;
	CaptureComponent->bCaptureOnMovement = false;
	CaptureComponent->bAlwaysPersistRenderingState = false;
	CaptureComponent->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_RenderScenePrimitives;
	CaptureComponent->CompositeMode = SCCM_Overwrite;
	Scene->AddComponent(CaptureComponent, FTransform::Identity);
}

void FIconStudioRenderer::ApplyLighting(const UIconStudioPreset* Preset, const FRotator& CameraRotation)
{
	using namespace IconStudioRendererLocal;

	auto Setup = [&CameraRotation](UDirectionalLightComponent* Component, const FIconStudioLight& Light)
	{
		Component->SetVisibility(Light.bEnabled);
		if (!Light.bEnabled)
		{
			return;
		}

		Component->SetWorldRotation(ComposeLightRotation(CameraRotation, Light));
		Component->SetIntensity(Light.Intensity);
		Component->SetLightColor(Light.Color);
		Component->SetCastShadows(Light.bCastShadows);
	};

	Setup(KeyLightComponent, Preset->KeyLight);
	Setup(FillLightComponent, Preset->FillLight);
	Setup(RimLightComponent, Preset->RimLight);

	// Recapturer le ciel est l'operation la plus chere de toute la mise en place :
	// on ne la refait que si la cubemap ou son intensite ont reellement change.
	UTextureCube* const Cubemap = Preset->SkyCubemap.LoadSynchronous();
	const bool bSkyDirty = (Cubemap != AppliedSkyCubemap) || !FMath::IsNearlyEqual(AppliedSkyIntensity, Preset->SkyIntensity);

	SkyLightComponent->SetVisibility(Cubemap != nullptr && Preset->SkyIntensity > 0.f);
	SkyLightComponent->SetLightColor(Preset->SkyColor);

	if (bSkyDirty)
	{
		SkyLightComponent->Cubemap = Cubemap;
		SkyLightComponent->SetIntensity(Preset->SkyIntensity);
		SkyLightComponent->RecaptureSky();
		Scene->UpdateCaptureContents();

		AppliedSkyCubemap = Cubemap;
		AppliedSkyIntensity = Preset->SkyIntensity;
	}
}

void FIconStudioRenderer::ApplyCamera(const UIconStudioPreset* Preset, const FRotator& CameraRotation, const FBoxSphereBounds& Bounds)
{
	const FRotationMatrix CameraBasis(CameraRotation);
	const FVector Forward = CameraBasis.GetUnitAxis(EAxis::X);
	const FVector Right = CameraBasis.GetUnitAxis(EAxis::Y);
	const FVector Up = CameraBasis.GetUnitAxis(EAxis::Z);

	// Etendue reelle de la boite englobante une fois projetee sur les axes de la
	// camera. Se contenter du rayon de la sphere englobante, comme on le voit
	// souvent, cadre bien trop large des qu'un objet est allonge : une planche
	// occuperait le tiers de son icone.
	const FVector Extent = Bounds.BoxExtent;
	auto ProjectExtent = [&Extent](const FVector& Axis)
	{
		return FMath::Abs(Axis.X * Extent.X) + FMath::Abs(Axis.Y * Extent.Y) + FMath::Abs(Axis.Z * Extent.Z);
	};

	const float HalfWidth = ProjectExtent(Right);
	const float HalfHeight = ProjectExtent(Up);
	const float HalfDepth = ProjectExtent(Forward);

	const float Padding = 1.f + FMath::Max(0.f, Preset->FramingPadding);
	const float FrameHalfSize = Preset->bUseFixedScale
		? FMath::Max(1.f, Preset->FixedFrameWidth) * 0.5f
		: FMath::Max(HalfWidth, HalfHeight) * Padding;

	// Recul : de quoi degager la profondeur de l'objet, plus une marge pour que
	// le plan proche ne vienne jamais trancher dedans.
	const float Distance = Preset->bOrthographic
		? HalfDepth + FrameHalfSize * 2.f + 100.f
		: FrameHalfSize / FMath::Tan(FMath::DegreesToRadians(Preset->FieldOfView * 0.5f)) + HalfDepth;

	const FVector Offset = Right * (Preset->FramingOffset.X * FrameHalfSize * 2.f)
		+ Up * (Preset->FramingOffset.Y * FrameHalfSize * 2.f);

	CaptureComponent->SetWorldLocationAndRotation(Bounds.Origin - Forward * Distance - Offset, CameraRotation);
	CaptureComponent->ProjectionType = Preset->bOrthographic ? ECameraProjectionMode::Orthographic : ECameraProjectionMode::Perspective;
	CaptureComponent->OrthoWidth = FrameHalfSize * 2.f;
	CaptureComponent->FOVAngle = Preset->FieldOfView;
	CaptureComponent->bOverride_CustomNearClippingPlane = true;
	CaptureComponent->CustomNearClippingPlane = 1.f;

	// Seuil de vide : tout ce qui est au-dela ne peut plus etre l'objet. Genereux
	// a dessein, la valeur du fond etant de plusieurs ordres de grandeur au-dessus.
	DepthThreshold = Distance + HalfDepth * 2.f + FrameHalfSize * 4.f + 1000.f;
}

void FIconStudioRenderer::ApplyPostProcess(const UIconStudioPreset* Preset)
{
	FPostProcessSettings& PP = CaptureComponent->PostProcessSettings;
	PP = FPostProcessSettings();
	CaptureComponent->PostProcessBlendWeight = 1.f;

	// Exposition figee. L'exposition automatique est le piege numero un de ce
	// genre d'outil : elle compense la luminosite moyenne de l'image, donc un
	// objet sombre serait eclairci et un objet clair assombri, et deux icones
	// cote a cote n'auraient plus la meme lumiere alors que le preset, lui,
	// n'a pas bouge.
	PP.bOverride_AutoExposureMethod = true;
	PP.AutoExposureMethod = AEM_Manual;
	PP.bOverride_AutoExposureBias = true;
	PP.AutoExposureBias = Preset->ExposureBias;
	PP.bOverride_AutoExposureMinBrightness = true;
	PP.AutoExposureMinBrightness = 1.f;
	PP.bOverride_AutoExposureMaxBrightness = true;
	PP.AutoExposureMaxBrightness = 1.f;

	PP.bOverride_BloomIntensity = true;
	PP.BloomIntensity = Preset->BloomIntensity;

	// Tout ce qui simule un objectif photo n'a rien a faire sur une icone : le
	// vignettage assombrit les coins, l'aberration colore les bords, le flou de
	// mouvement bave sur un objet qui ne bouge pas.
	PP.bOverride_VignetteIntensity = true;
	PP.VignetteIntensity = 0.f;
	PP.bOverride_SceneFringeIntensity = true;
	PP.SceneFringeIntensity = 0.f;
	PP.bOverride_MotionBlurAmount = true;
	PP.MotionBlurAmount = 0.f;
	PP.bOverride_DepthOfFieldFocalDistance = true;
	PP.DepthOfFieldFocalDistance = 0.f;
	PP.bOverride_AmbientOcclusionIntensity = true;
	PP.AmbientOcclusionIntensity = Preset->bAmbientOcclusion ? 0.5f : 0.f;

	FEngineShowFlags& Flags = CaptureComponent->ShowFlags;

	// L'antialiasing du moteur doit rester eteint : le masque de transparence
	// vient de la profondeur, qui n'est jamais lissee, alors que la couleur le
	// serait -- les deux ne coincideraient plus sur les bords. La suranalyse
	// fait ce travail bien mieux, et de facon identique sur les deux passes.
	Flags.SetAntiAliasing(false);
	Flags.SetTemporalAA(false);
	Flags.SetMotionBlur(false);

	// Rien de tout cela n'existe dans une scene de studio, mais un show flag
	// laisse actif suffit a colorer le fond ou a voiler l'objet.
	Flags.SetAtmosphere(false);
	Flags.SetFog(false);
	Flags.SetVolumetricFog(false);
	Flags.SetGrid(false);
	Flags.SetSelectionOutline(false);
	Flags.SetLensFlares(false);
	Flags.SetScreenSpaceReflections(false);
	Flags.SetToneCurve(true);
	Flags.SetEyeAdaptation(true);
}

UTextureRenderTarget2D* FIconStudioRenderer::EnsureTarget(TObjectPtr<UTextureRenderTarget2D>& Target, int32 Size, bool bFloatingPoint)
{
	if (Target && Target->SizeX == Size && Target->SizeY == Size)
	{
		return Target;
	}

	Target = NewObject<UTextureRenderTarget2D>(GetTransientPackage(), NAME_None, RF_Transient);
	// RTF_RGBA8 et non sa variante sRGB : la capture LDR ecrit deja des valeurs
	// encodees en gamma, un second encodage par le materiel les delaverait.
	Target->RenderTargetFormat = bFloatingPoint ? RTF_RGBA16f : RTF_RGBA8;
	Target->ClearColor = FLinearColor::Black;
	Target->bAutoGenerateMips = false;
	Target->InitAutoFormat(Size, Size);
	Target->UpdateResourceImmediate(true);

	return Target;
}

bool FIconStudioRenderer::CaptureTo(UTextureRenderTarget2D* Target, uint8 CaptureSource)
{
	if (!Target)
	{
		return false;
	}

	CaptureComponent->TextureTarget = Target;
	CaptureComponent->CaptureSource = static_cast<ESceneCaptureSource>(CaptureSource);

	// Le monde de preview n'est pas tique : sans cet appel, les changements de
	// transformation et de mesh faits juste au-dessus ne sont pas encore parvenus
	// au thread de rendu, et la capture montrerait l'objet precedent.
	if (UWorld* World = Scene->GetWorld())
	{
		World->SendAllEndOfFrameUpdates();
	}

	CaptureComponent->CaptureScene();
	return true;
}

bool FIconStudioRenderer::Render(UStaticMesh* Mesh, UMaterialInterface* MaterialOverride, const UIconStudioPreset* Preset, EIconStudioPass Pass, FIconStudioImage& OutImage)
{
	using namespace IconStudioRendererLocal;

	if (!Mesh || !Preset)
	{
		return false;
	}

	EnsureScene();

	MeshComponent->EmptyOverrideMaterials();
	MeshComponent->SetStaticMesh(Mesh);
	if (MaterialOverride)
	{
		MeshComponent->SetMaterial(0, MaterialOverride);
	}
	MeshComponent->SetWorldTransform(FTransform::Identity);

	// Sans ca, une texture encore en cours de streaming rend l'icone floue --
	// et comme le monde n'est pas tique, le streaming ne rattraperait jamais son
	// retard tout seul. StreamAllResources bloque le temps qu'il faut ; c'est
	// acceptable pour un outil d'editeur, et c'est la seule facon d'avoir un
	// resultat reproductible entre le premier rendu et les suivants.
	MeshComponent->SetTextureForceResidentFlag(true);
	MeshComponent->PrestreamTextures(60.f, /*bPrioritizeCharacterTextures=*/false);
	IStreamingManager::Get().StreamAllResources(0.f);

	const FRotator CameraRotation(Preset->CameraPitch, Preset->CameraYaw, 0.f);
	const FBoxSphereBounds Bounds = Mesh->GetBounds();

	ApplyLighting(Preset, CameraRotation);
	ApplyCamera(Preset, CameraRotation, Bounds);
	ApplyPostProcess(Preset);

	const int32 FinalSize = FMath::Max(16, Preset->Resolution);
	const int32 Supersample = FMath::Clamp(Preset->SupersampleFactor, 1, 8);
	const int32 RenderSize = FinalSize * Supersample;

	UTextureRenderTarget2D* const Color = EnsureTarget(ColorTarget, RenderSize, /*bFloatingPoint=*/false);
	UTextureRenderTarget2D* const Depth = EnsureTarget(DepthTarget, RenderSize, /*bFloatingPoint=*/true);

	// Passe de masque d'abord : c'est elle qui decide de ce qui existe, et la
	// passe de couleur ne fait que l'habiller.
	CaptureTo(Depth, SCS_SceneDepth);
	TArray<FFloat16Color> DepthPixels;
	if (!Depth->GameThread_GetRenderTargetResource()->ReadFloat16Pixels(DepthPixels))
	{
		return false;
	}

	uint8 ColorSource = SCS_FinalColorLDR;
	bool bColorIsSRGB = true;
	switch (Pass)
	{
	case EIconStudioPass::BaseColor:
		ColorSource = SCS_BaseColor;
		// Le G-Buffer stocke l'albedo tel quel : le lire comme du sRGB
		// l'eclaircirait deux fois.
		bColorIsSRGB = false;
		break;
	case EIconStudioPass::Normal:
		ColorSource = SCS_Normal;
		bColorIsSRGB = false;
		break;
	case EIconStudioPass::Depth:
		ColorSource = SCS_DeviceDepth;
		bColorIsSRGB = false;
		break;
	default:
		break;
	}

	CaptureTo(Color, ColorSource);
	TArray<FColor> ColorPixels;
	FReadSurfaceDataFlags ReadFlags(RCM_UNorm);
	ReadFlags.SetLinearToGamma(false);
	if (!Color->GameThread_GetRenderTargetResource()->ReadPixels(ColorPixels, ReadFlags))
	{
		return false;
	}

	const int32 Expected = RenderSize * RenderSize;
	if (ColorPixels.Num() < Expected || DepthPixels.Num() < Expected)
	{
		return false;
	}

	// ------------------------------------------------------------------
	// Reduction : un pixel final = SupersampleFactor^2 pixels de rendu.
	// La couleur est moyennee sur les seuls sous-pixels couverts, ce qui donne
	// directement une couleur non premultipliee -- pas de division par l'alpha,
	// donc pas de bruit sur les bords tres transparents.
	// ------------------------------------------------------------------
	TArray<FLinearColor> Accum;
	TArray<float> Coverage;
	Accum.SetNumZeroed(FinalSize * FinalSize);
	Coverage.SetNumZeroed(FinalSize * FinalSize);

	const float SubPixelCount = static_cast<float>(Supersample * Supersample);

	for (int32 Y = 0; Y < FinalSize; ++Y)
	{
		for (int32 X = 0; X < FinalSize; ++X)
		{
			FLinearColor Sum = FLinearColor(0.f, 0.f, 0.f, 0.f);
			int32 Covered = 0;

			for (int32 SubY = 0; SubY < Supersample; ++SubY)
			{
				const int32 SourceRow = (Y * Supersample + SubY) * RenderSize;
				for (int32 SubX = 0; SubX < Supersample; ++SubX)
				{
					const int32 SourceIndex = SourceRow + X * Supersample + SubX;
					const float SampleDepth = DepthPixels[SourceIndex].R.GetFloat();
					if (!(SampleDepth > 0.f && SampleDepth < DepthThreshold))
					{
						continue;
					}

					const FColor& Raw = ColorPixels[SourceIndex];
					// Moyenner des valeurs encodees en gamma assombrit les
					// degrades ; on repasse en lineaire le temps de l'operation.
					// L'alpha du render target n'a lui aucun sens ici -- il est
					// force a 1 pour ne compter que les sous-pixels, jamais leur
					// canal alpha parasite.
					FLinearColor Sample = bColorIsSRGB
						? FLinearColor(Raw)
						: FLinearColor(Raw.R / 255.f, Raw.G / 255.f, Raw.B / 255.f, 1.f);
					Sample.A = 1.f;
					Sum += Sample;
					++Covered;
				}
			}

			const int32 Index = Y * FinalSize + X;
			Coverage[Index] = Covered / SubPixelCount;
			Accum[Index] = Covered > 0 ? Sum / static_cast<float>(Covered) : FLinearColor(0.f, 0.f, 0.f, 0.f);
		}
	}

	// ------------------------------------------------------------------
	// Etalement des bords : un pixel entierement transparent n'a aucune couleur
	// definie. Laisse a noir, il se melange aux pixels voisins des que l'icone
	// est filtree ou reduite, et l'objet se retrouve cerne d'un liseré sombre.
	// On propage donc la couleur du bord vers l'exterieur, sans toucher a l'alpha.
	// ------------------------------------------------------------------
	for (int32 PassIndex = 0; PassIndex < FMath::Clamp(Preset->EdgeBleedPasses, 0, 8); ++PassIndex)
	{
		TArray<FLinearColor> Bled = Accum;
		bool bAnyFilled = false;

		for (int32 Y = 0; Y < FinalSize; ++Y)
		{
			for (int32 X = 0; X < FinalSize; ++X)
			{
				const int32 Index = Y * FinalSize + X;
				if (Coverage[Index] > 0.f || Accum[Index].A > 0.f)
				{
					continue;
				}

				FLinearColor Sum(0.f, 0.f, 0.f, 0.f);
				int32 Count = 0;
				for (int32 OffsetY = -1; OffsetY <= 1; ++OffsetY)
				{
					for (int32 OffsetX = -1; OffsetX <= 1; ++OffsetX)
					{
						const int32 NeighbourX = X + OffsetX;
						const int32 NeighbourY = Y + OffsetY;
						if (NeighbourX < 0 || NeighbourY < 0 || NeighbourX >= FinalSize || NeighbourY >= FinalSize)
						{
							continue;
						}

						const int32 NeighbourIndex = NeighbourY * FinalSize + NeighbourX;
						if (Coverage[NeighbourIndex] > 0.f || Accum[NeighbourIndex].A > 0.f)
						{
							Sum += Accum[NeighbourIndex];
							++Count;
						}
					}
				}

				if (Count > 0)
				{
					Bled[Index] = Sum / static_cast<float>(Count);
					// L'alpha sert ici de marqueur "ce pixel a recu une couleur",
					// pour que la passe suivante puisse s'appuyer dessus. Il est
					// remis a la couverture reelle au moment de l'encodage.
					Bled[Index].A = 1.f;
					bAnyFilled = true;
				}
			}
		}

		if (!bAnyFilled)
		{
			break;
		}
		Accum = MoveTemp(Bled);
	}

	// ------------------------------------------------------------------
	// Encodage final
	// ------------------------------------------------------------------
	const bool bSolid = Preset->BackgroundMode == EIconStudioBackground::SolidColor;
	const FLinearColor Background = Preset->BackgroundColor;

	OutImage.Size = FinalSize;
	OutImage.Pixels.SetNumUninitialized(FinalSize * FinalSize);

	for (int32 Index = 0; Index < FinalSize * FinalSize; ++Index)
	{
		const float Alpha = Coverage[Index];
		FLinearColor Linear = Accum[Index];
		Linear.A = 1.f;

		if (bSolid)
		{
			// Le fond opaque est compose ici, et non rendu : changer sa couleur
			// ne coute alors pas un seul rendu, et les deux modes partagent
			// exactement le meme masque.
			Linear = Linear * Alpha + Background * (1.f - Alpha);
			Linear.A = 1.f;
		}

		FColor Encoded = bColorIsSRGB ? Linear.ToFColor(/*bSRGB=*/true) : Linear.ToFColor(/*bSRGB=*/false);
		Encoded.A = bSolid ? 255 : static_cast<uint8>(FMath::RoundToInt(FMath::Clamp(Alpha, 0.f, 1.f) * 255.f));
		OutImage.Pixels[Index] = Encoded;
	}

	return true;
}

UTexture2D* FIconStudioRenderer::UpdatePreviewTexture(const FIconStudioImage& Image)
{
	if (!Image.IsValid())
	{
		return PreviewTexture;
	}

	if (!PreviewTexture || PreviewTexture->GetSizeX() != Image.Size || PreviewTexture->GetSizeY() != Image.Size)
	{
		// Pas d'AddToRoot : le renderer est un FGCObject, il declare deja cette
		// texture dans AddReferencedObjects. L'enraciner en plus la rendrait
		// immortelle, y compris apres la fermeture de l'outil.
		PreviewTexture = UTexture2D::CreateTransient(Image.Size, Image.Size, PF_B8G8R8A8);
		PreviewTexture->SRGB = true;
		PreviewTexture->Filter = TF_Bilinear;
	}

	FTexture2DMipMap& Mip = PreviewTexture->GetPlatformData()->Mips[0];
	void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);
	FMemory::Memcpy(Data, Image.Pixels.GetData(), Image.Pixels.Num() * sizeof(FColor));
	Mip.BulkData.Unlock();
	PreviewTexture->UpdateResource();

	return PreviewTexture;
}
