Files
UnrealEngine/Engine/Source/Runtime/TextureUtilitiesCommon/Private/TextureSourceDataUtils.cpp
2025-05-18 13:04:45 +08:00

775 lines
21 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "TextureSourceDataUtils.h"
#if WITH_EDITOR
// TextureSourceDataUtils is *incorrectly* in /Runtime/
// then the whole file is wrapped in WITH_EDITOR
#include "ImageCoreUtils.h"
#include "Engine/Texture.h"
#include "HAL/UnrealMemory.h"
#include "EngineLogs.h"
#include "TextureImportSettings.h"
#include "TextureBuildUtilities.h"
#include "ImageUtils.h"
namespace UE::TextureUtilitiesCommon::Experimental
{
namespace Private
{
// adds wrap/clamp flags for X/Y on the filter as appropriate for this texture
static FImageCore::EResizeImageFilter SetResizeImageFilterEdgeModes(UTexture * Texture,FImageCore::EResizeImageFilter Filter)
{
using namespace FImageCore;
// default Filter with no flags will clamp at edges
if ( Texture->GetTextureClass() == ETextureClass::Cube ||
Texture->GetTextureClass() == ETextureClass::CubeArray )
{
if ( Texture->Source.IsLongLatCubemap() )
{
// wrap X, clamp Y
return Filter | EResizeImageFilter::Flag_WrapX;
}
else
{
// clamp cube faces
return Filter;
}
}
else
{
// see ComputeAddressMode
if ( Texture->bPreserveBorder )
{
// clamp
return Filter;
}
// @@ nonpow2, UI, NoMipMaps -> clamp ?
if ( Texture->GetTextureAddressX() == TA_Wrap )
{
Filter |= EResizeImageFilter::Flag_WrapX;
}
if ( Texture->GetTextureAddressY() == TA_Wrap )
{
Filter |= EResizeImageFilter::Flag_WrapY;
}
return Filter;
}
}
// use ResizeImage instead of DownsizeImageUsingTextureSettings ?
static bool UseResizeImageInsteadOfTextureSettings(UTexture * Texture)
{
if ( Texture->Source.IsLongLatCubemap() )
{
// longlat needs to wrap X and clamp Y which the TextureSettings resize can't do
return true;
}
if ( Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::StretchToPowerOfTwo ||
Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::StretchToSquarePowerOfTwo )
{
if ( ! Texture->Source.AreAllBlocksPowerOfTwo() )
{
// stretching required, ResizeImage instead
return true;
}
}
if ( Texture->MipGenSettings == TMGS_NoMipmaps ||
Texture->MipGenSettings == TMGS_LeaveExistingMips ||
Texture->MipGenSettings == TMGS_Angular )
{
// TextureSettings mip gen is ill defined in this case, don't use it
return true;
}
return false;
}
// resize so that the largest dimension is <= MaxSize
static bool ResizeTexture2D(UTexture* Texture, int32 MaxSize, const ITargetPlatform* TargetPlatform)
{
check( Texture->Source.GetNumLayers() == 1 );
check( Texture->Source.GetNumBlocks() == 1 );
const int32 LayerIndex = 0;
// We want to reduce the asset size so ignore the imported mip(s) (??)
const int32 MipIndex = 0;
FImage Image;
if (!Texture->Source.GetMipImage(Image, MipIndex))
{
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2D: Texture GetMipImage failed [%s]"),
*Texture->GetFullName());
return false;
}
if ( UseResizeImageInsteadOfTextureSettings(Texture) )
{
int32 TargetSizeX = Image.SizeX;
int32 TargetSizeY = Image.SizeY;
// stretch to pow2 like TextureCompressorModule
if ( Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::StretchToPowerOfTwo )
{
TargetSizeX = FMath::RoundUpToPowerOfTwo(Image.SizeX);
TargetSizeY = FMath::RoundUpToPowerOfTwo(Image.SizeY);
}
else if ( Texture->PowerOfTwoMode == ETexturePowerOfTwoSetting::StretchToSquarePowerOfTwo )
{
TargetSizeX = TargetSizeY = FMath::RoundUpToPowerOfTwo( FMath::Max(Image.SizeX,Image.SizeY) );
}
// do halving steps to get <= MaxSize
while( TargetSizeX > MaxSize || TargetSizeY > MaxSize )
{
TargetSizeX = FMath::Max(1,TargetSizeX>>1);
TargetSizeY = FMath::Max(1,TargetSizeY>>1);
}
if ( TargetSizeX >= Image.SizeX && TargetSizeY >= Image.SizeY )
{
// nothing to do
return false;
}
FImageCore::EResizeImageFilter Filter = SetResizeImageFilterEdgeModes(Texture,FImageCore::EResizeImageFilter::Default);
FImageCore::ResizeImageInPlace(Image,TargetSizeX,TargetSizeY,Filter);
}
else
{
bool MadeChanges = false;
if ( ! Texture->DownsizeImageUsingTextureSettings(TargetPlatform, Image, MaxSize, LayerIndex, MadeChanges) )
{
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2D: Texture DownsizeImageUsingTextureSettings failed [%s]"),
*Texture->GetFullName());
return false;
}
if ( ! MadeChanges )
{
return false;
}
}
Texture->PreEditChange(nullptr);
Texture->Source.Init(Image);
// if gamma was Pow22 it is now sRGB
Texture->bUseLegacyGamma = false;
// PostEditChange is called outside by our caller
return true;
}
static int32 RoundToNearestInt32PowerOfTwo(double X)
{
double Log2X = FMath::Log2(X);
int32 IntLog2X = FMath::RoundToInt32(Log2X);
return 1 << FMath::Clamp(IntLog2X,0,30);
}
// resize so that the largest dimension is == MaxSize
static bool ResizeTextureToNearestPow2(UTexture* Texture)
{
check( Texture->Source.GetNumLayers() == 1 );
check( Texture->Source.GetNumBlocks() == 1 );
const int32 LayerIndex = 0;
// discard imported mips :
const int32 MipIndex = 0;
FImage Image;
if (!Texture->Source.GetMipImage(Image, MipIndex))
{
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2D: Texture GetMipImage failed [%s]"),
*Texture->GetFullName());
return false;
}
check( Image.SizeX == Texture->Source.GetSizeX() );
check( Image.SizeY == Texture->Source.GetSizeY() );
int32 TargetSizeX,TargetSizeY;
// make the larger dimension go to nearest pow2 first
// then fix the smaller dimension for aspect ratio
if ( Image.SizeX >= Image.SizeY )
{
TargetSizeX = RoundToNearestInt32PowerOfTwo(Image.SizeX);
TargetSizeY = RoundToNearestInt32PowerOfTwo( (double) TargetSizeX * Image.SizeY / Image.SizeX );
}
else
{
TargetSizeY = RoundToNearestInt32PowerOfTwo(Image.SizeY);
TargetSizeX = RoundToNearestInt32PowerOfTwo( (double) TargetSizeY * Image.SizeX / Image.SizeY );
}
FImageCore::EResizeImageFilter Filter = SetResizeImageFilterEdgeModes(Texture,FImageCore::EResizeImageFilter::Default);
FImageCore::ResizeImageInPlace(Image,TargetSizeX,TargetSizeY,Filter);
Texture->PreEditChange(nullptr);
Texture->Source.Init(Image);
// if gamma was Pow22 it is now sRGB
Texture->bUseLegacyGamma = false;
// PostEditChange is called outside by our caller
return true;
}
// concatenate all the image payloads into one bulkdata, eg. for mips or blocks
static UE::Serialization::FEditorBulkData::FSharedBufferWithID MakeSharedBufferForImageDatas(const TArray<FImage> & InImages)
{
int64 SizeNeededInBytes = 0;
for (const FImage& Im : InImages)
{
check( Im.RawData.Num() == Im.GetImageSizeBytes() );
SizeNeededInBytes += Im.RawData.Num();
}
FUniqueBuffer WriteImageBuffer = FUniqueBuffer::Alloc(SizeNeededInBytes);
uint8* CurrentAddress = static_cast<uint8*>(WriteImageBuffer.GetData());
for (const FImage& Im : InImages)
{
FMemory::Memcpy(CurrentAddress, Im.RawData.GetData(), Im.RawData.Num());
CurrentAddress += Im.RawData.Num();
}
return WriteImageBuffer.MoveToShared();
}
static bool ResizeTexture2DBlocked(UTexture* Texture, int32 TotalMaxSize, const ITargetPlatform* TargetPlatform)
{
// does not support layers
check( Texture->Source.GetNumLayers() == 1 );
const int32 NumLayers = 1;
const int32 LayerIndex = 0;
check( Texture->Source.GetNumBlocks() > 1 );
// MaxSize is applied to the total UDIM size
FIntPoint LogicalSourceSize = Texture->Source.GetLogicalSize();
check( LogicalSourceSize.X > TotalMaxSize || LogicalSourceSize.Y > TotalMaxSize );
double ResizeRatio = double(TotalMaxSize) / FMath::Max(LogicalSourceSize.X,LogicalSourceSize.Y);
check( ResizeRatio < 1.0 );
TArray<FTextureSourceBlock> ResizedSourceBlocks;
ResizedSourceBlocks.Reserve(Texture->Source.GetNumBlocks());
TArray<FImage> ResizedBlocks;
ResizedBlocks.Reserve(Texture->Source.GetNumBlocks());
bool MadeAnyChanges = false;
for (int32 BlockIndex = 0; BlockIndex < Texture->Source.GetNumBlocks(); ++BlockIndex)
{
// We want to reduce the asset size so ignore the imported mip(s)
FImage SourceMip0;
const int32 MipIndex = 0;
if (!Texture->Source.GetMipImage(SourceMip0, BlockIndex, LayerIndex, MipIndex))
{
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2DBlocked: Texture GetMipImage failed [%s]"),
*Texture->GetFullName());
return false;
}
int32 NewSizeX = FMath::RoundToInt32( ResizeRatio * SourceMip0.SizeX );
int32 NewSizeY = FMath::RoundToInt32( ResizeRatio * SourceMip0.SizeY );
int32 BlockMaxSize = FMath::Max(NewSizeX,NewSizeY);
bool MadeChanges;
if ( ! Texture->DownsizeImageUsingTextureSettings(TargetPlatform, SourceMip0, BlockMaxSize, LayerIndex, MadeChanges) )
{
// critical error
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2DBlocked: Texture DownsizeImageUsingTextureSettings failed [%s]"),
*Texture->GetFullName());
return false;
}
MadeAnyChanges = MadeAnyChanges || MadeChanges;
FTextureSourceBlock& ResizedSourceBlock = ResizedSourceBlocks.AddDefaulted_GetRef();
Texture->Source.GetBlock(BlockIndex, ResizedSourceBlock);
FImage& ResizedBlock = ResizedBlocks.AddDefaulted_GetRef();
ResizedBlock = MoveTemp(SourceMip0);
ResizedSourceBlock.SizeX = ResizedBlock.SizeX;
ResizedSourceBlock.SizeY = ResizedBlock.SizeY;
ResizedSourceBlock.NumMips = 1;
}
if ( ! MadeAnyChanges )
{
return false;
}
// Protect the code from an async build of the texture
Texture->PreEditChange(nullptr);
UE::Serialization::FEditorBulkData::FSharedBufferWithID ResizedImageBufferWithID = MakeSharedBufferForImageDatas(ResizedBlocks);
const ETextureSourceFormat SourceFormat = Texture->Source.GetFormat();
Texture->Source.InitBlocked(
&SourceFormat, // array of formats per layer
ResizedSourceBlocks.GetData(),
NumLayers,
ResizedSourceBlocks.Num(),
MoveTemp(ResizedImageBufferWithID)
);
// if gamma was Pow22 it is now sRGB
Texture->bUseLegacyGamma = false;
return true;
}
static bool ResizeTextureToNearestPow2Blocked(UTexture* Texture)
{
// does not support layers
check( Texture->Source.GetNumLayers() == 1 );
const int32 NumLayers = 1;
const int32 LayerIndex = 0;
const int32 NumBlocks = Texture->Source.GetNumBlocks();
check( NumBlocks > 1 );
// Resize to Pow2 acts on each UDIM block, not the net size
TArray<FTextureSourceBlock> SourceBlocks;
SourceBlocks.SetNum(NumBlocks);
int32 BlockMaxSizeX = 0;
int32 BlockMaxSizeY = 0;
// get the largest of all blocks (same as VT builder)
for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex)
{
Texture->Source.GetBlock(BlockIndex, SourceBlocks[BlockIndex]);
BlockMaxSizeX = FMath::Max(BlockMaxSizeX, SourceBlocks[BlockIndex].SizeX );
BlockMaxSizeY = FMath::Max(BlockMaxSizeY, SourceBlocks[BlockIndex].SizeY );
}
int32 TargetSizeX,TargetSizeY;
// make the larger dimension go to nearest pow2 first
// then fix the smaller dimension for aspect ratio
if ( BlockMaxSizeX >= BlockMaxSizeY )
{
TargetSizeX = RoundToNearestInt32PowerOfTwo(BlockMaxSizeX);
TargetSizeY = RoundToNearestInt32PowerOfTwo( (double) TargetSizeX * BlockMaxSizeY / BlockMaxSizeX );
}
else
{
TargetSizeY = RoundToNearestInt32PowerOfTwo(BlockMaxSizeY);
TargetSizeX = RoundToNearestInt32PowerOfTwo( (double) TargetSizeY * BlockMaxSizeX / BlockMaxSizeY );
}
TArray<FImage> ResizedBlocks;
ResizedBlocks.SetNum(NumBlocks);
for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex)
{
FImage & SourceMip0 = ResizedBlocks[BlockIndex];
// Drops any imported mip data (yuck?)
const int32 MipIndex = 0;
if (!Texture->Source.GetMipImage(SourceMip0, BlockIndex, LayerIndex, MipIndex))
{
UE_LOG(LogTexture,Error,TEXT("ResizeTexture2DBlocked: Texture GetMipImage failed [%s]"),
*Texture->GetFullName());
return false;
}
// all blocks have to be pow2 and the same aspect ratio
// but they don't have to all be the same size
// if they were different sizes, we could use a mip of TargetSize here; eg. (TargetSizeX>>1)
// if that was closer to the original size
// don't bother for now, just make them all the same size
// if this block is == TargetSize, this is a nop
FImageCore::ResizeImageInPlace(SourceMip0,TargetSizeX,TargetSizeY);
SourceBlocks[BlockIndex].SizeX = TargetSizeX;
SourceBlocks[BlockIndex].SizeY = TargetSizeY;
SourceBlocks[BlockIndex].NumMips = 1;
}
// Protect the code from an async build of the texture
Texture->PreEditChange(nullptr);
UE::Serialization::FEditorBulkData::FSharedBufferWithID ResizedImageBufferWithID = MakeSharedBufferForImageDatas(ResizedBlocks);
const ETextureSourceFormat SourceFormat = Texture->Source.GetFormat();
Texture->Source.InitBlocked(
&SourceFormat, // array of formats per layer
SourceBlocks.GetData(),
NumLayers,
NumBlocks,
MoveTemp(ResizedImageBufferWithID)
);
// if gamma was Pow22 it is now sRGB
Texture->bUseLegacyGamma = false;
return true;
}
} // namespace Private
static bool IsTextureResizeSupported(UTexture * Texture)
{
if ( ! ensure( Texture->Source.IsValid() ) )
{
return false;
}
// Check if we don't know how to resize that texture
if ( Texture->Source.GetNumMips() > 1 && Texture->MipGenSettings == TMGS_LeaveExistingMips )
{
// should not do this if MipGen == LeaveExisting ; or maybe warn?
// return false;
// go ahead and do it, but warn:
UE_LOG(LogTexture,Warning,TEXT("DownsizeTextureSourceData: Texture has LeaveExistingMips ; they will be discarded! [%s]"),
*Texture->GetFullName());
}
// we only support 1 layer currently
if (Texture->Source.GetNumLayers() != 1)
{
return false;
}
if (!(Texture->Source.GetTextureClass() == ETextureClass::Cube || Texture->Source.GetTextureClass() == ETextureClass::TwoD))
{
// array, cubearray, volume, not supported
return false;
}
if (Texture->Source.GetTextureClass() == ETextureClass::Cube)
{
if (Texture->Source.IsLongLatCubemap())
{
if (Texture->Source.GetNumSlices() != 1)
{
return false;
}
}
else if (Texture->Source.GetNumSlices() != 6)
{
return false;
}
}
return true;
}
TEXTUREUTILITIESCOMMON_API bool ResizeTextureSourceDataToNearestPowerOfTwo(UTexture* Texture)
{
if ( ! IsTextureResizeSupported(Texture) )
{
return false;
}
if ( Texture->Source.AreAllBlocksPowerOfTwo() )
{
return false;
}
if (Texture->Source.GetNumBlocks() == 1)
{
return Private::ResizeTextureToNearestPow2(Texture);
}
else
{
// UDIM VT
return Private::ResizeTextureToNearestPow2Blocked(Texture);
}
}
TEXTUREUTILITIESCOMMON_API bool DownsizeTextureSourceData(UTexture* Texture, int32 TargetSourceSize, const ITargetPlatform* TargetPlatform)
{
if ( ! IsTextureResizeSupported(Texture) )
{
return false;
}
FIntPoint SourceSize = Texture->Source.GetLogicalSize();
if (SourceSize.X <= TargetSourceSize && SourceSize.Y <= TargetSourceSize)
{
return false;
}
if (Texture->Source.GetNumBlocks() == 1)
{
return Private::ResizeTexture2D(Texture, TargetSourceSize, TargetPlatform);
}
else
{
// UDIM VT
return Private::ResizeTexture2DBlocked(Texture, TargetSourceSize, TargetPlatform);
}
}
TEXTUREUTILITIESCOMMON_API bool DownsizeTextureSourceDataNearRenderingSize(UTexture* Texture, const ITargetPlatform* TargetPlatform, int32 AdditionalSourceSizeLimit)
{
if ( ! Texture->Source.IsValid() )
{
return false;
}
int32 BeforeSizeX;
int32 BeforeSizeY;
Texture->GetBuiltTextureSize(TargetPlatform, BeforeSizeX, BeforeSizeY);
int32 TargetSizeInGame = FMath::Max(BeforeSizeX, BeforeSizeY);
int32 TargetSourceSize = TargetSizeInGame;
if (Texture->Source.IsLongLatCubemap())
{
// The function return the max size of the generated cube from the source long lat
TargetSourceSize = UE::TextureBuildUtilities::ComputeLongLatCubemapExtents(TargetSizeInGame,0);
}
TargetSourceSize = FMath::Min(TargetSourceSize,AdditionalSourceSizeLimit);
if (DownsizeTextureSourceData(Texture, TargetSourceSize, TargetPlatform))
{
Texture->LODBias = 0;
// this counts as a reimport :
UE::TextureUtilitiesCommon::ApplyDefaultsForNewlyImportedTextures(Texture,true);
Texture->PostEditChange();
// check that GetBuiltTextureSize was preserved :
int32 AfterSizeX;
int32 AfterSizeY;
Texture->GetBuiltTextureSize(TargetPlatform, AfterSizeX, AfterSizeY);
if ( BeforeSizeX != AfterSizeX ||
BeforeSizeY != AfterSizeY )
{
UE_LOG(LogTexture,Warning,TEXT("DownsizeTextureSourceDataNearRenderingSize failed to preserve built size; was: %dx%d now: %dx%d on [%s]"),
BeforeSizeX,BeforeSizeY,
AfterSizeX,AfterSizeY,
*Texture->GetFullName());
}
return true;
}
// PreEditChange may have been called even if DownsizeTextureSourceData return false
// we don't PostEditChange here
// that's okay but not great
return false;
}
TEXTUREUTILITIESCOMMON_API bool ChangeTextureSourceFormat(UTexture* Texture, ETextureSourceFormat NewFormat)
{
if ( ! Texture->Source.IsValid() )
{
return false;
}
// we only support 1 layer currently
if (Texture->Source.GetNumLayers() != 1)
{
return false;
}
ETextureSourceFormat OldFormat = Texture->Source.GetFormat(0);
if ( OldFormat == NewFormat )
{
return false;
}
ERawImageFormat::Type NewRIF = FImageCoreUtils::ConvertToRawImageFormat(NewFormat);
EGammaSpace NewGamma = ( Texture->SRGB && ERawImageFormat::GetFormatNeedsGammaSpace(NewRIF) ) ? EGammaSpace::sRGB : EGammaSpace::Linear;
if ( Texture->Source.GetNumBlocks() == 1 && Texture->Source.GetNumMips() == 1 )
{
const int32 MipIndex = 0;
FImage SourceMip;
if (!Texture->Source.GetMipImage(SourceMip, MipIndex))
{
UE_LOG(LogTexture,Error,TEXT("ChangeTextureSourceFormat: Texture GetMipImage failed [%s]"),
*Texture->GetFullName());
return false;
}
FImage NewMip;
SourceMip.CopyTo(NewMip,NewRIF,NewGamma);
Texture->PreEditChange(nullptr);
Texture->Source.Init(NewMip);
}
else // blocks and/or mips
{
// all blocks of a UDIM have the same format; Layers do not
int32 NumLayers = 1;
int32 LayerIndex = 0;
int32 NumBlocks = Texture->Source.GetNumBlocks();
check( NumBlocks >= 1 );
TArray<FTextureSourceBlock> NewBlocks;
NewBlocks.Reserve(NumBlocks);
TArray<FImage> NewImages;
NewImages.Reserve(NumBlocks*16); // *16 for mips
for (int32 BlockIndex = 0; BlockIndex < NumBlocks; ++BlockIndex)
{
FTextureSourceBlock Block;
Texture->Source.GetBlock(BlockIndex,Block);
// NewBlocks has sizes, they don't change
NewBlocks.Add(Block);
for(int32 MipIndex=0; MipIndex < Block.NumMips;MipIndex++)
{
FImage SourceMip;
if ( ! Texture->Source.GetMipImage(SourceMip, BlockIndex, LayerIndex, MipIndex) )
{
UE_LOG(LogTexture,Error,TEXT("ChangeTextureSourceFormat: Texture GetMipImage failed [%s]"),
*Texture->GetFullName());
return false;
}
FImage & NewMip = NewImages.AddDefaulted_GetRef();
SourceMip.CopyTo(NewMip,NewRIF,NewGamma);
}
}
UE::Serialization::FEditorBulkData::FSharedBufferWithID ResizedImageBufferWithID = Private::MakeSharedBufferForImageDatas(NewImages);
Texture->PreEditChange(nullptr);
Texture->Source.InitBlocked(
&NewFormat, // array of formats per layer
NewBlocks.GetData(),
NumLayers,
NewBlocks.Num(),
MoveTemp(ResizedImageBufferWithID)
);
}
// if gamma was Pow22 it is now sRGB
Texture->bUseLegacyGamma = false;
// this counts as a reimport :
UE::TextureUtilitiesCommon::ApplyDefaultsForNewlyImportedTextures(Texture,true);
Texture->PostEditChange();
check( Texture->Source.GetGammaSpace(0) == NewGamma );
return true;
}
// calls Pre/PostEditChange :
TEXTUREUTILITIESCOMMON_API bool CompressTextureSourceWithJPEG(UTexture* Texture,int32 Quality)
{
if ( ! Texture->Source.IsValid() )
{
return false;
}
// we only support 1 layer currently
if (Texture->Source.GetNumLayers() != 1)
{
return false;
}
if (Texture->Source.GetNumBlocks() != 1 )
{
// JPEG does not support UDIM/blocks ; fix me?
return false;
}
if (Texture->Source.GetSourceCompression() == ETextureSourceCompressionFormat::TSCF_JPEG )
{
// already JPEG
return false;
}
if ( Texture->Source.GetNumSlices() != 1 )
{
// 1 mip, 1 slice only
return false;
}
ETextureSourceFormat Format = Texture->Source.GetFormat(0);
if ( Format != TSF_G8 && Format != TSF_BGRA8 )
{
// must be 8 bit
return false;
}
// we do kill existing mips to match the behavior of the other conversions in here
// okay, looks good, do it!
FImage Image;
if ( ! Texture->Source.GetMipImage(Image,0) )
{
UE_LOG(LogTexture,Error,TEXT("CompressTextureSourceWithJPEG: Texture GetMipImage failed [%s]"),
*Texture->GetFullName());
return false;
}
// JPEG it :
TArray64<uint8> JPEGData;
if ( ! FImageUtils::CompressImage(JPEGData,TEXT(".jpg"),Image,Quality) )
{
UE_LOG(LogTexture,Error,TEXT("CompressTextureSourceWithJPEG: Texture CompressImage failed [%s]"),
*Texture->GetFullName());
return false;
}
Texture->PreEditChange(nullptr);
// Format stays BGRA8 or G8
const int32 NumMips = 1;
Texture->Source.InitWithCompressedSourceData(Image.SizeX,Image.SizeY,NumMips,Format,
JPEGData,
ETextureSourceCompressionFormat::TSCF_JPEG);
Texture->PostEditChange();
return true;
}
} // End namespace UE::TextureUtilitiesCommon::Experimental
#endif //WITH_EDITOR