941 lines
23 KiB
C++
941 lines
23 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MuR/Image.h"
|
|
|
|
#include "HAL/UnrealMemory.h"
|
|
#include "Math/UnrealMathSSE.h"
|
|
#include "Math/NumericLimits.h"
|
|
#include "MuR/ImagePrivate.h"
|
|
#include "MuR/MutableMath.h"
|
|
#include "MuR/MutableTrace.h"
|
|
|
|
namespace mu
|
|
{
|
|
|
|
FImage::FImage()
|
|
{
|
|
}
|
|
|
|
FImage::FImage(uint32 SizeX, uint32 SizeY, uint32 Lods, EImageFormat Format, EInitializationType InitType)
|
|
{
|
|
Init(SizeX, SizeY, Lods, Format, InitType);
|
|
}
|
|
|
|
void FImage::Init(uint32 SizeX, uint32 SizeY, uint32 Lods, EImageFormat Format, EInitializationType InitType)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(NewImage)
|
|
LLM_SCOPE_BYNAME(TEXT("MutableRuntime"));
|
|
|
|
const FImageDesc ImageDesc {FImageSize(uint16(SizeX), uint16(SizeY)), Format, static_cast<uint8>(Lods)};
|
|
DataStorage.Init(ImageDesc, InitType);
|
|
}
|
|
|
|
|
|
void FImage::InitToBlack()
|
|
{
|
|
const FImageDesc Desc = DataStorage.MakeImageDesc();
|
|
DataStorage.Init(Desc, EInitializationType::Black);
|
|
|
|
Flags = 0;
|
|
RelevancyMinY = 0;
|
|
RelevancyMaxY = 0;
|
|
}
|
|
|
|
|
|
TSharedPtr<FImage> FImage::CreateAsReference(uint32 ID, const FImageDesc& Desc, bool bForceLoad)
|
|
{
|
|
TSharedPtr<FImage> Result = MakeShared<FImage>();
|
|
Result->ReferenceID = ID;
|
|
|
|
Result->DataStorage.InitVoid(Desc);
|
|
|
|
Result->Flags = EImageFlags::IF_IS_REFERENCE;
|
|
if (bForceLoad)
|
|
{
|
|
Result->Flags = Result->Flags | EImageFlags::IF_IS_FORCELOAD;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
void FImage::Serialise(const FImage* ValuePtr, FOutputArchive& Arch)
|
|
{
|
|
Arch << *ValuePtr;
|
|
}
|
|
|
|
void FImage::Serialise(FOutputArchive& Arch) const
|
|
{
|
|
Arch << DataStorage;
|
|
|
|
// Remove non-persistent flags.
|
|
uint8 flags = Flags & ~IF_HAS_RELEVANCY_MAP;
|
|
Arch << flags;
|
|
}
|
|
|
|
void FImage::Unserialise(FInputArchive& Arch)
|
|
{
|
|
Arch >> DataStorage;
|
|
Arch >> Flags;
|
|
}
|
|
|
|
|
|
TSharedPtr<FImage> FImage::StaticUnserialise(FInputArchive& Arch)
|
|
{
|
|
LLM_SCOPE_BYNAME(TEXT("MutableRuntime"));
|
|
|
|
TSharedPtr<FImage> ResultPtr = MakeShared<FImage>();
|
|
Arch >> *ResultPtr;
|
|
|
|
return ResultPtr;
|
|
}
|
|
|
|
int32 FImage::GetDataSize() const
|
|
{
|
|
return DataStorage.GetAllocatedSize();
|
|
}
|
|
|
|
TSharedPtr<FImage> FImageOperator::ExtractMip(const FImage* This, int32 Mip)
|
|
{
|
|
if (Mip == 0 && This->GetLODCount() == 1)
|
|
{
|
|
return CloneImage(This);
|
|
}
|
|
|
|
FIntVector2 MipSize = This->CalculateMipSize(Mip);
|
|
|
|
constexpr int32 Quality = 4;
|
|
|
|
if (This->GetLODCount() > Mip)
|
|
{
|
|
TArrayView<const uint8> SrcView = This->DataStorage.GetLOD(Mip);
|
|
|
|
TSharedPtr<FImage> ResultImage = CreateImage(MipSize.X, MipSize.Y, 1, This->GetFormat(), EInitializationType::NotInitialized);
|
|
ResultImage->Flags = This->Flags;
|
|
|
|
// Probably an RLE texture, resize.
|
|
if (ResultImage->DataStorage.IsEmpty())
|
|
{
|
|
ResultImage->DataStorage.ResizeLOD(0, SrcView.Num());
|
|
}
|
|
|
|
TArrayView<uint8> DestView = ResultImage->DataStorage.GetLOD(0);
|
|
|
|
check(DestView.Num() == SrcView.Num())
|
|
FMemory::Memcpy(DestView.GetData(), SrcView.GetData(), DestView.Num());
|
|
|
|
return ResultImage;
|
|
}
|
|
|
|
// We need to generate the mip
|
|
// \TODO: optimize, quality
|
|
|
|
TSharedPtr<FImage> Resized = CreateImage(MipSize.X, MipSize.Y, 1, This->GetFormat(), EInitializationType::NotInitialized);
|
|
ImageResizeLinear(Resized.Get(), Quality, This);
|
|
|
|
//TODO: Do we realy need to extract the mip again? The resized Image looks already to be what we want.
|
|
TSharedPtr<FImage> Result = ExtractMip(Resized.Get(), 0);
|
|
ReleaseImage(Resized);
|
|
|
|
return Result;
|
|
}
|
|
|
|
uint16 FImage::GetSizeX() const
|
|
{
|
|
return GetSize().X;
|
|
}
|
|
|
|
uint16 FImage::GetSizeY() const
|
|
{
|
|
return GetSize().Y;
|
|
}
|
|
|
|
const FImageSize& FImage::GetSize() const
|
|
{
|
|
return DataStorage.ImageSize;
|
|
}
|
|
|
|
EImageFormat FImage::GetFormat() const
|
|
{
|
|
return DataStorage.ImageFormat;
|
|
}
|
|
|
|
int32 FImage::GetLODCount() const
|
|
{
|
|
return FMath::Max(1, (int32)DataStorage.NumLODs);
|
|
}
|
|
|
|
const uint8* FImage::GetLODData(int32 LOD) const
|
|
{
|
|
return DataStorage.GetLOD(LOD).GetData();
|
|
}
|
|
|
|
uint8* FImage::GetLODData(int32 LOD)
|
|
{
|
|
return DataStorage.GetLOD(LOD).GetData();
|
|
}
|
|
|
|
int32 FImage::GetLODDataSize(int32 LOD) const
|
|
{
|
|
return DataStorage.GetLOD(LOD).Num();
|
|
}
|
|
|
|
bool FImage::IsReference() const
|
|
{
|
|
return Flags & EImageFlags::IF_IS_REFERENCE;
|
|
}
|
|
|
|
bool FImage::IsForceLoad() const
|
|
{
|
|
return Flags & EImageFlags::IF_IS_FORCELOAD;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
uint32 FImage::GetReferencedTexture() const
|
|
{
|
|
ensure(IsReference());
|
|
return ReferenceID;
|
|
}
|
|
|
|
int32 FImage::CalculateDataSize(int32 SizeX, int32 SizeY, int32 LODCount, EImageFormat Format)
|
|
{
|
|
int32 Result = 0;
|
|
|
|
const FImageFormatData& FormatData = GetImageFormatData(Format);
|
|
if (FormatData.BytesPerBlock)
|
|
{
|
|
for (int32 LODIndex = 0; LODIndex < FMath::Max(1, LODCount); ++LODIndex)
|
|
{
|
|
int32 BlocksX = FMath::DivideAndRoundUp(SizeX, int32(FormatData.PixelsPerBlockX));
|
|
int32 BlocksY = FMath::DivideAndRoundUp(SizeY, int32(FormatData.PixelsPerBlockY));
|
|
|
|
Result += (BlocksX * BlocksY) * FormatData.BytesPerBlock;
|
|
|
|
SizeX = FMath::DivideAndRoundUp(SizeX, 2);
|
|
SizeY = FMath::DivideAndRoundUp(SizeY, 2);
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
FIntVector2 FImage::CalculateMipSize(int32 Mip) const
|
|
{
|
|
FIntVector2 Result = FIntVector2(GetSizeX(), GetSizeY());
|
|
|
|
for (int32 L = 0; L < Mip + 1; ++L)
|
|
{
|
|
if (L == Mip)
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
Result[0] = FMath::DivideAndRoundUp(Result[0], 2);
|
|
Result[1] = FMath::DivideAndRoundUp(Result[1], 2);
|
|
}
|
|
|
|
return FIntVector2(0, 0);
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
int32 FImage::GetMipmapCount(int32 SizeX, int32 SizeY)
|
|
{
|
|
if (SizeX <= 0 || SizeY <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int32 MaxLevel = FMath::CeilLogTwo(FMath::Max(SizeX, SizeY)) + 1;
|
|
return MaxLevel;
|
|
}
|
|
|
|
|
|
const uint8* FImage::GetMipData(int32 Mip) const
|
|
{
|
|
return DataStorage.GetLOD(Mip).GetData();
|
|
}
|
|
|
|
|
|
uint8* FImage::GetMipData(int32 Mip)
|
|
{
|
|
return DataStorage.GetLOD(Mip).GetData();
|
|
}
|
|
|
|
int32 FImage::GetMipsDataSize() const
|
|
{
|
|
return DataStorage.GetDataSize();
|
|
}
|
|
|
|
FVector4f FImage::Sample(FVector2f Coords) const
|
|
{
|
|
FVector4f Result = FVector4f(0.0f);
|
|
|
|
if (GetSizeX() == 0 || GetSizeY() == 0)
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
const FImageFormatData& FormatData = GetImageFormatData(GetFormat());
|
|
|
|
FImageSize Size = GetSize();
|
|
EImageFormat Format = GetFormat();
|
|
|
|
// TODO: This looks like it should support block compression, but it is not implemented.
|
|
// Decide if it's worth implement it or simplify the ByteOffset computation assuming no
|
|
// compression is allowed.
|
|
|
|
int32 PixelX = FMath::Max(0, FMath::Min(Size.X - 1, (int32)(Coords.X * Size.X)));
|
|
int32 BlockX = PixelX / FormatData.PixelsPerBlockX;
|
|
int32 BlockPixelX = PixelX % FormatData.PixelsPerBlockX;
|
|
|
|
int32 PixelY = FMath::Max(0, FMath::Min(Size.Y - 1, (int32)(Coords.Y * Size.Y)));
|
|
int32 BlockY = PixelY / FormatData.PixelsPerBlockY;
|
|
int32 BlockPixelY = PixelY % FormatData.PixelsPerBlockY;
|
|
|
|
int32 BlocksPerRow = FMath::DivideAndRoundUp(int32(Size.X), int32(FormatData.PixelsPerBlockX));
|
|
int32 BlockOffset = BlockX + BlockY * BlocksPerRow;
|
|
|
|
const TArrayView<const uint8> Data = DataStorage.GetLOD(0);
|
|
|
|
// Non-generic part
|
|
if (Format == EImageFormat::RGB_UByte)
|
|
{
|
|
int32 ByteOffset = BlockOffset * FormatData.BytesPerBlock
|
|
+ (BlockPixelY * FormatData.PixelsPerBlockX + BlockPixelX) * 3;
|
|
|
|
Result[0] = Data[ByteOffset + 0] / 255.0f;
|
|
Result[1] = Data[ByteOffset + 1] / 255.0f;
|
|
Result[2] = Data[ByteOffset + 2] / 255.0f;
|
|
Result[3] = 1.0f;
|
|
}
|
|
else if (Format == EImageFormat::RGBA_UByte)
|
|
{
|
|
int32 ByteOffset = BlockOffset * FormatData.BytesPerBlock
|
|
+ (BlockPixelY * FormatData.PixelsPerBlockX + BlockPixelX) * 4;
|
|
|
|
Result[0] = Data[ByteOffset + 0] / 255.0f;
|
|
Result[1] = Data[ByteOffset + 1] / 255.0f;
|
|
Result[2] = Data[ByteOffset + 2] / 255.0f;
|
|
Result[3] = Data[ByteOffset + 3] / 255.0f;
|
|
}
|
|
else if (Format == EImageFormat::BGRA_UByte)
|
|
{
|
|
int32 ByteOffset = BlockOffset * FormatData.BytesPerBlock
|
|
+ (BlockPixelY * FormatData.PixelsPerBlockX + BlockPixelX) * 4;
|
|
|
|
Result[0] = Data[ByteOffset + 2] / 255.0f;
|
|
Result[1] = Data[ByteOffset + 1] / 255.0f;
|
|
Result[2] = Data[ByteOffset + 0] / 255.0f;
|
|
Result[3] = Data[ByteOffset + 3] / 255.0f;
|
|
}
|
|
else if (Format== EImageFormat::L_UByte)
|
|
{
|
|
int32 ByteOffset = BlockOffset * FormatData.BytesPerBlock
|
|
+ (BlockPixelY * FormatData.PixelsPerBlockX + BlockPixelX) * 1;
|
|
|
|
Result[0] = Data[ByteOffset] / 255.0f;
|
|
Result[1] = Data[ByteOffset] / 255.0f;
|
|
Result[2] = Data[ByteOffset] / 255.0f;
|
|
Result[3] = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
check(false);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
bool FImage::IsPlainColour(FVector4f& OutColor) const
|
|
{
|
|
bool Result = true;
|
|
|
|
const TArrayView<const uint8> DataView = DataStorage.GetLOD(0);
|
|
|
|
if (DataView.Num())
|
|
{
|
|
switch(GetFormat())
|
|
{
|
|
case EImageFormat::L_UByte:
|
|
{
|
|
uint8 const * const DataPtr = DataView.GetData();
|
|
|
|
const int32 NumElems = DataView.Num();
|
|
int32 I = 0;
|
|
for (; I < NumElems; ++I)
|
|
{
|
|
if (FMemory::Memcmp(DataPtr, DataPtr + I*1, 1) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Result = I >= NumElems;
|
|
OutColor = FVector4f(*DataPtr, *DataPtr, *DataPtr, 255) / 255.0f;
|
|
|
|
break;
|
|
}
|
|
|
|
case EImageFormat::RGB_UByte:
|
|
{
|
|
uint8 const * const DataPtr = DataView.GetData();
|
|
|
|
const int32 NumElems = DataView.Num() / 3;
|
|
int32 I = 0;
|
|
for (; I < NumElems; ++I)
|
|
{
|
|
if (FMemory::Memcmp(DataPtr, DataPtr + I*3, 3) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Result = I >= NumElems;
|
|
OutColor = FVector4f(DataPtr[0], DataPtr[1], DataPtr[2], 255) / 255.0f;
|
|
|
|
break;
|
|
}
|
|
|
|
case EImageFormat::RGBA_UByte:
|
|
case EImageFormat::BGRA_UByte:
|
|
{
|
|
uint8 const * const DataPtr = DataView.GetData();
|
|
|
|
const int32 NumElems = DataView.Num() / 4;
|
|
int32 I = 0;
|
|
for (; I < NumElems; ++I)
|
|
{
|
|
if (FMemory::Memcmp(DataPtr, DataPtr + I*4, 4) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
Result = I >= NumElems;
|
|
if (GetFormat() == EImageFormat::RGBA_UByte)
|
|
{
|
|
OutColor = FVector4f(DataPtr[0], DataPtr[1], DataPtr[2], DataPtr[3]) / 255.0f;
|
|
}
|
|
else if (GetFormat() == EImageFormat::BGRA_UByte)
|
|
{
|
|
OutColor = FVector4f(DataPtr[2], DataPtr[1], DataPtr[0], DataPtr[3]) / 255.0f;
|
|
}
|
|
else
|
|
{
|
|
check(false);
|
|
}
|
|
|
|
|
|
break;
|
|
}
|
|
|
|
// TODO: Other formats could also be implemented. For compressed types,
|
|
// the compressed block could be compared and only uncompress if all are the same to
|
|
// check if all pixels in the block are also equal.
|
|
default:
|
|
Result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
bool FImage::IsFullAlpha() const
|
|
{
|
|
const TArrayView<const uint8> DataView = DataStorage.GetLOD(0);
|
|
|
|
if (DataView.Num())
|
|
{
|
|
switch(GetFormat())
|
|
{
|
|
case EImageFormat::RGBA_UByte:
|
|
case EImageFormat::BGRA_UByte:
|
|
{
|
|
const uint8* DataPtr = DataView.GetData() + 3;
|
|
|
|
const int32 NumElems = DataView.Num() / 4;
|
|
for (int32 I = 0; I < NumElems; ++I)
|
|
{
|
|
if (*(DataPtr + I*4) != 255)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
case EImageFormat::RGB_UByte:
|
|
{
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
namespace
|
|
{
|
|
bool IsZero(const uint8* Buff, SIZE_T Size)
|
|
{
|
|
if (Size == 0)
|
|
{
|
|
return true;
|
|
}
|
|
return (*Buff == 0) && (FMemory::Memcmp(Buff, Buff + 1, Size - 1) == 0);
|
|
}
|
|
}
|
|
|
|
|
|
void FImage::GetNonBlackRect_Reference(FImageRect& Rect) const
|
|
{
|
|
const FImageSize Size = GetSize();
|
|
Rect.min[0] = Rect.min[1] = 0;
|
|
Rect.size[0] = Size.X;
|
|
Rect.size[1] = Size.Y;
|
|
|
|
if (!Rect.size[0] || !Rect.size[1])
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bFirst = true;
|
|
|
|
// Slow reference implementation
|
|
uint16 Top = 0;
|
|
uint16 Left = 0;
|
|
uint16 Right = Size.X - 1;
|
|
uint16 Bottom = Size.Y - 1;
|
|
|
|
const EImageFormat Format = GetFormat();
|
|
|
|
const TArrayView<const uint8> DataView = DataStorage.GetLOD(0);
|
|
|
|
const uint8 * const DataPtr = DataView.GetData();
|
|
|
|
for (uint16 Y = 0; Y < Size.Y; ++Y)
|
|
{
|
|
for (uint16 X = 0; X < Size.X; ++X)
|
|
{
|
|
bool bIsBlack = false;
|
|
|
|
switch (Format)
|
|
{
|
|
case EImageFormat::L_UByte:
|
|
{
|
|
bIsBlack = DataPtr[Y*Size.X + X] == 0;
|
|
break;
|
|
}
|
|
|
|
case EImageFormat::RGB_UByte:
|
|
{
|
|
constexpr uint32 ZeroValue = 0;
|
|
bIsBlack = FMemory::Memcmp(&ZeroValue, DataPtr + (Y*Size.X + X)*3, 3) == 0;
|
|
|
|
break;
|
|
}
|
|
|
|
case EImageFormat::RGBA_UByte:
|
|
case EImageFormat::BGRA_UByte:
|
|
{
|
|
constexpr uint32 ZeroValue = 0;
|
|
bIsBlack = FMemory::Memcmp(&ZeroValue, DataPtr + (Y*Size.X + X)*4, 4) == 0;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
|
|
if (!bIsBlack)
|
|
{
|
|
if (bFirst)
|
|
{
|
|
bFirst = false;
|
|
Left = Right = X;
|
|
Top = Bottom = Y;
|
|
}
|
|
else
|
|
{
|
|
Left = FMath::Min(Left, X);
|
|
Right = FMath::Max(Right, X);
|
|
Top = FMath::Min(Top, Y);
|
|
Bottom = FMath::Max(Bottom, Y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rect.min[0] = Left;
|
|
Rect.min[1] = Top;
|
|
Rect.size[0] = Right - Left + 1;
|
|
Rect.size[1] = Bottom - Top + 1;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
void FImage::GetNonBlackRect(FImageRect& rect) const
|
|
{
|
|
// TODO: There is a bug here with cyborg-windows.
|
|
// Meanwhile do this.
|
|
GetNonBlackRect_Reference(rect);
|
|
return;
|
|
|
|
// rect.min[0] = rect.min[1] = 0;
|
|
// rect.size = m_size;
|
|
|
|
//check(rect.size[0] > 0);
|
|
//check(rect.size[1] > 0);
|
|
|
|
// if ( !rect.size[0] || !rect.size[1] )
|
|
// return;
|
|
|
|
// uint16 sx = m_size[0];
|
|
// uint16 sy = m_size[1];
|
|
|
|
// // Somewhat faster implementation
|
|
// uint16 top = 0;
|
|
// uint16 left = 0;
|
|
// uint16 right = sx - 1;
|
|
// uint16 bottom = sy - 1;
|
|
|
|
// switch ( m_format )
|
|
// {
|
|
// case L_UByte:
|
|
// {
|
|
// size_t rowStride = sx;
|
|
|
|
// // Find top
|
|
// const uint8_t* pRow = m_data.GetData();
|
|
// while ( top < sy )
|
|
// {
|
|
// if ( !is_zero( pRow, rowStride ) )
|
|
// break;
|
|
// pRow += rowStride;
|
|
// ++top;
|
|
// }
|
|
|
|
// // Find bottom
|
|
// pRow = m_data.GetData() + rowStride * bottom;
|
|
// while ( bottom > top )
|
|
// {
|
|
// if ( !is_zero( pRow, rowStride ) )
|
|
// break;
|
|
// pRow -= rowStride;
|
|
// --bottom;
|
|
// }
|
|
|
|
// // Find left and right
|
|
// left = sx - 1;
|
|
// right = 0;
|
|
// int16_t currentRow = top;
|
|
// pRow = m_data.GetData() + rowStride * currentRow;
|
|
// while ( currentRow <= bottom && ( left > 0 || right < ( sx - 1 ) ) )
|
|
// {
|
|
// for ( uint16 x = 0; x < left; ++x )
|
|
// {
|
|
// if ( pRow[x] )
|
|
// {
|
|
// left = x;
|
|
// break;
|
|
// }
|
|
// }
|
|
// for ( uint16 x = sx - 1; x > right; --x )
|
|
// {
|
|
// if ( pRow[x] )
|
|
// {
|
|
// right = x;
|
|
// break;
|
|
// }
|
|
// }
|
|
// pRow += rowStride;
|
|
// ++currentRow;
|
|
// }
|
|
|
|
// break;
|
|
// }
|
|
|
|
// case RGB_UByte:
|
|
// case RGBA_UByte:
|
|
// case BGRA_UByte:
|
|
// {
|
|
// size_t bytesPerPixel = GetImageFormatData( m_format ).BytesPerBlock;
|
|
// size_t rowStride = sx * bytesPerPixel;
|
|
|
|
// // Find top
|
|
// const uint8_t* pRow = m_data.GetData();
|
|
// while(top<sy)
|
|
// {
|
|
// if ( !is_zero( pRow, rowStride ) )
|
|
// break;
|
|
// pRow += rowStride;
|
|
// ++top;
|
|
// }
|
|
|
|
// // Find bottom
|
|
// pRow = m_data.GetData() + rowStride * bottom;
|
|
// while ( bottom > top )
|
|
// {
|
|
// if ( !is_zero( pRow, rowStride ) )
|
|
// break;
|
|
// pRow -= rowStride;
|
|
// --bottom;
|
|
// }
|
|
|
|
// // Find left and right
|
|
// left = sx - 1;
|
|
// right = 0;
|
|
// int16_t currentRow = top;
|
|
// pRow = m_data.GetData() + rowStride * currentRow;
|
|
// uint8_t zeroPixel[16] = { 0 };
|
|
// check(bytesPerPixel<16);
|
|
// while ( currentRow <= bottom && (left > 0 || right<(sx-1)) )
|
|
// {
|
|
// for ( uint16 x = 0; x < left; ++x )
|
|
// {
|
|
// if ( memcmp( pRow + x * bytesPerPixel, zeroPixel, bytesPerPixel ) )
|
|
// {
|
|
// left = x;
|
|
// break;
|
|
// }
|
|
// }
|
|
// for ( uint16 x = sx - 1; x > right; --x )
|
|
// {
|
|
// if ( memcmp( pRow + x * bytesPerPixel, zeroPixel, bytesPerPixel ) )
|
|
// {
|
|
// right = x;
|
|
// break;
|
|
// }
|
|
// }
|
|
// pRow += rowStride;
|
|
// ++currentRow;
|
|
// }
|
|
|
|
// break;
|
|
// }
|
|
|
|
// default:
|
|
// check(false);
|
|
// break;
|
|
// }
|
|
|
|
// rect.min[0] = left;
|
|
// rect.min[1] = top;
|
|
// rect.size[0] = right - left + 1;
|
|
// rect.size[1] = bottom - top + 1;
|
|
|
|
// // debug
|
|
// FImageRect debugRect;
|
|
// GetNonBlackRect_Reference( debugRect );
|
|
// if (!(rect==debugRect))
|
|
// {
|
|
// mu::Halt();
|
|
// }
|
|
}
|
|
|
|
void FImage::ReduceLODsTo(int32 NewLODCount)
|
|
{
|
|
DataStorage.SetNumLODs(NewLODCount);
|
|
}
|
|
|
|
void FImage::ReduceLODs(int32 LODsToSkip)
|
|
{
|
|
DataStorage.DropLODs(LODsToSkip);
|
|
}
|
|
|
|
void FImageOperator::FillColor(FImage* Target, FVector4f Color)
|
|
{
|
|
const EImageFormat Format = Target->GetFormat();
|
|
|
|
switch (Format)
|
|
{
|
|
case EImageFormat::RGB_UByte:
|
|
{
|
|
constexpr int32 BatchSizeInElems = 1 << 14;
|
|
constexpr int32 ElemSizeInBytes = 3;
|
|
const int32 NumBatches = Target->DataStorage.GetNumBatches(BatchSizeInElems, ElemSizeInBytes);
|
|
|
|
for (int32 I = 0; I < NumBatches; ++I)
|
|
{
|
|
TArrayView<uint8> DataView = Target->DataStorage.GetBatch(I, BatchSizeInElems, ElemSizeInBytes);
|
|
|
|
const int32 PixelCount = DataView.Num()/ElemSizeInBytes;
|
|
uint8* DataPtr = DataView.GetData();
|
|
uint32 R = uint32(FMath::Clamp(255.0f * Color[0], 0.0f, 255.0f));
|
|
uint32 G = uint32(FMath::Clamp(255.0f * Color[1], 0.0f, 255.0f));
|
|
uint32 B = uint32(FMath::Clamp(255.0f * Color[2], 0.0f, 255.0f));
|
|
|
|
const uint32 PixelData = R | (G << 8) | (B << 16);
|
|
|
|
if (PixelData == 0)
|
|
{
|
|
FMemory::Memzero(DataPtr, PixelCount*3);
|
|
continue;
|
|
}
|
|
|
|
if ((R == G) & (R == B))
|
|
{
|
|
FMemory::Memset(DataPtr, static_cast<uint8>(R), PixelCount*3);
|
|
continue;
|
|
}
|
|
|
|
const uint64 TwoPixelsData = (uint64(PixelData) << 24) | PixelData;
|
|
|
|
for (int32 P = 0; P < PixelCount >> 1; ++P)
|
|
{
|
|
FMemory::Memcpy(&DataPtr[P * 6], &TwoPixelsData, 6);
|
|
}
|
|
|
|
if (PixelCount & 1)
|
|
{
|
|
FMemory::Memcpy(&DataPtr[(PixelCount >> 1) * 6], &PixelData, 3);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EImageFormat::RGBA_UByte:
|
|
case EImageFormat::BGRA_UByte:
|
|
{
|
|
constexpr int32 BatchSizeInElems = 1 << 14;
|
|
constexpr int32 ElemSizeInBytes = 4;
|
|
const int32 NumBatches = Target->DataStorage.GetNumBatches(BatchSizeInElems, ElemSizeInBytes);
|
|
|
|
uint32 R = uint32(FMath::Clamp(255.0f * Color[0], 0.0f, 255.0f));
|
|
uint32 G = uint32(FMath::Clamp(255.0f * Color[1], 0.0f, 255.0f));
|
|
uint32 B = uint32(FMath::Clamp(255.0f * Color[2], 0.0f, 255.0f));
|
|
uint32 A = uint32(FMath::Clamp(255.0f * Color[3], 0.0f, 255.0f));
|
|
|
|
const uint32 PixelData = Format == EImageFormat::RGBA_UByte
|
|
? R | (G << 8) | (B << 16) | (A << 24)
|
|
: B | (G << 8) | (R << 16) | (A << 24);
|
|
|
|
for (int32 I = 0; I < NumBatches; ++I)
|
|
{
|
|
TArrayView<uint8> DataView = Target->DataStorage.GetBatch(I, BatchSizeInElems, ElemSizeInBytes);
|
|
|
|
const int32 PixelCount = DataView.Num()/ElemSizeInBytes;
|
|
uint8* DataPtr = DataView.GetData();
|
|
|
|
if (PixelData == 0)
|
|
{
|
|
FMemory::Memzero(DataPtr, PixelCount*4);
|
|
continue;
|
|
}
|
|
|
|
if ((R == G) & (R == B) & (R == A))
|
|
{
|
|
FMemory::Memset(DataPtr, static_cast<uint8>(R), PixelCount*4);
|
|
continue;
|
|
}
|
|
|
|
const uint64 TwoPixelsData = (uint64(PixelData) << 32) | PixelData;
|
|
|
|
for (int32 P = 0; P < (PixelCount >> 1); ++P)
|
|
{
|
|
FMemory::Memcpy(&DataPtr[P * 8], &TwoPixelsData, 8);
|
|
}
|
|
|
|
if (PixelCount & 1)
|
|
{
|
|
FMemory::Memcpy(&DataPtr[(PixelCount >> 1) * 8], &PixelData, 4);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case EImageFormat::L_UByte:
|
|
{
|
|
constexpr int32 BatchSizeInElems = 1 << 14;
|
|
constexpr int32 ElemSizeInBytes = 1;
|
|
const int32 NumBatches = Target->DataStorage.GetNumBatches(BatchSizeInElems, ElemSizeInBytes);
|
|
|
|
uint32 R = uint32(FMath::Clamp(255.0f * Color[0], 0.0f, 255.0f));
|
|
|
|
for (int32 I = 0; I < NumBatches; ++I)
|
|
{
|
|
TArrayView<uint8> DataView = Target->DataStorage.GetBatch(I, BatchSizeInElems, ElemSizeInBytes);
|
|
|
|
const int32 PixelCount = DataView.Num()/ElemSizeInBytes;
|
|
uint8* DataPtr = DataView.GetData();
|
|
|
|
if (R == 0)
|
|
{
|
|
FMemory::Memzero(DataPtr, PixelCount*1);
|
|
continue;
|
|
}
|
|
|
|
FMemory::Memset(DataPtr, static_cast<uint8>(R), PixelCount*1);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
// Generic case that supports compressed formats.
|
|
const FImageFormatData& FormatData = GetImageFormatData(Format);
|
|
TSharedPtr<FImage> BlockImage = CreateImage(
|
|
FormatData.PixelsPerBlockX, FormatData.PixelsPerBlockY, 1, EImageFormat::RGBA_UByte, EInitializationType::NotInitialized);
|
|
|
|
const uint32 PixelData =
|
|
(uint32(FMath::Clamp(255.0f * Color[0], 0.0f, 255.0f)) << 0 ) |
|
|
(uint32(FMath::Clamp(255.0f * Color[1], 0.0f, 255.0f)) << 8 ) |
|
|
(uint32(FMath::Clamp(255.0f * Color[2], 0.0f, 255.0f)) << 16) |
|
|
(uint32(FMath::Clamp(255.0f * Color[3], 0.0f, 255.0f)) << 24);
|
|
|
|
uint8 * const UncompressedBlockData = BlockImage->GetLODData(0);
|
|
const int32 UncompressedPixelCount = FormatData.PixelsPerBlockX * FormatData.PixelsPerBlockY;
|
|
for (int32 I = 0; I < UncompressedPixelCount; ++I)
|
|
{
|
|
FMemory::Memcpy(UncompressedBlockData + I*4, &PixelData, 4);
|
|
}
|
|
|
|
TSharedPtr<FImage> Converted = ImagePixelFormat(0, BlockImage.Get(), Format);
|
|
ReleaseImage(BlockImage);
|
|
|
|
const TArrayView<const uint8> BlockView = Converted->DataStorage.GetLOD(0);
|
|
|
|
constexpr int32 BatchSizeInElems = 1 << 14;
|
|
const int32 ElemSizeInBytes = FormatData.BytesPerBlock;
|
|
check(BlockView.Num() == ElemSizeInBytes);
|
|
const int32 NumBatches = Target->DataStorage.GetNumBatches(BatchSizeInElems, ElemSizeInBytes);
|
|
|
|
uint8 const * const BlockData = BlockView.GetData();
|
|
for (int32 I = 0; I < NumBatches; ++I)
|
|
{
|
|
TArrayView<uint8> DataView = Target->DataStorage.GetBatch(I, BatchSizeInElems, ElemSizeInBytes);
|
|
|
|
check(DataView.Num() % ElemSizeInBytes == 0);
|
|
|
|
const int32 BatchNumBlocks = DataView.Num() / ElemSizeInBytes;
|
|
uint8* DataPtr = DataView.GetData();
|
|
|
|
|
|
for (int32 BlockIndex = 0; BlockIndex < BatchNumBlocks; ++BlockIndex)
|
|
{
|
|
FMemory::Memcpy(DataPtr + BlockIndex*ElemSizeInBytes, BlockData, ElemSizeInBytes);
|
|
}
|
|
}
|
|
|
|
ReleaseImage(Converted);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|