You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
439 lines
11 KiB
C++
439 lines
11 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ImageWrapperPrivatePCH.h"
|
|
|
|
#if WITH_UNREALEXR
|
|
/**
|
|
* EXR image wrapper class.
|
|
*/
|
|
|
|
FExrImageWrapper::FExrImageWrapper()
|
|
: FImageWrapperBase()
|
|
{
|
|
}
|
|
|
|
template <typename sourcetype> class FSourceImageRaw
|
|
{
|
|
public:
|
|
FSourceImageRaw(const TArray<uint8>& SourceImageBitmapIN, uint32 ChannelsIN, uint32 WidthIN, uint32 HeightIN)
|
|
: SourceImageBitmap(SourceImageBitmapIN),
|
|
Width( WidthIN ),
|
|
Height(HeightIN),
|
|
Channels(ChannelsIN)
|
|
{
|
|
check(SourceImageBitmap.Num() == Channels*Width*Height*sizeof(sourcetype));
|
|
}
|
|
|
|
uint32 GetXStride() const {return sizeof(sourcetype) * Channels;}
|
|
uint32 GetYStride() const {return GetXStride() * Width;}
|
|
|
|
const sourcetype* GetHorzLine(uint32 y, uint32 Channel)
|
|
{
|
|
return &SourceImageBitmap[(sizeof(sourcetype) * Channel) + (GetYStride() * y)];
|
|
}
|
|
|
|
private:
|
|
const TArray<uint8>& SourceImageBitmap;
|
|
uint32 Width;
|
|
uint32 Height;
|
|
uint32 Channels;
|
|
};
|
|
|
|
|
|
class FMemFileOut : public Imf::OStream
|
|
{
|
|
public:
|
|
//-------------------------------------------------------
|
|
// A constructor that opens the file with the given name.
|
|
// The destructor will close the file.
|
|
//-------------------------------------------------------
|
|
|
|
FMemFileOut(const char fileName[]) :
|
|
Imf::OStream(fileName),
|
|
Pos(0)
|
|
{
|
|
}
|
|
|
|
virtual void write(const char c[/*n*/], int n)
|
|
{
|
|
for (int32 i = 0; i < n; ++i)
|
|
{
|
|
Data[Pos + i] = c[i];
|
|
}
|
|
Pos += n;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// Get the current writing position, in bytes from the
|
|
// beginning of the file. If the next call to write() will
|
|
// start writing at the beginning of the file, tellp()
|
|
// returns 0.
|
|
//---------------------------------------------------------
|
|
|
|
virtual Imf::Int64 tellp()
|
|
{
|
|
return Pos;
|
|
}
|
|
|
|
|
|
//-------------------------------------------
|
|
// Set the current writing position.
|
|
// After calling seekp(i), tellp() returns i.
|
|
//-------------------------------------------
|
|
|
|
virtual void seekp(Imf::Int64 pos)
|
|
{
|
|
Pos = pos;
|
|
}
|
|
|
|
|
|
int64 Pos;
|
|
TArray<uint8> Data;
|
|
};
|
|
|
|
|
|
class FMemFileIn : public Imf::IStream
|
|
{
|
|
public:
|
|
//-------------------------------------------------------
|
|
// A constructor that opens the file with the given name.
|
|
// The destructor will close the file.
|
|
//-------------------------------------------------------
|
|
|
|
FMemFileIn(const void* InData, int32 InSize)
|
|
: Imf::IStream("")
|
|
, Data((const char *)InData)
|
|
, Size(InSize)
|
|
, Pos(0)
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------
|
|
// Read from the stream:
|
|
//
|
|
// read(c,n) reads n bytes from the stream, and stores
|
|
// them in array c. If the stream contains less than n
|
|
// bytes, or if an I/O error occurs, read(c,n) throws
|
|
// an exception. If read(c,n) reads the last byte from
|
|
// the file it returns false, otherwise it returns true.
|
|
//------------------------------------------------------
|
|
|
|
virtual bool read (char c[/*n*/], int n)
|
|
{
|
|
if(Pos + n > Size)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int32 i = 0; i < n; ++i)
|
|
{
|
|
c[i] = Data[Pos];
|
|
++Pos;
|
|
}
|
|
|
|
return Pos >= Size;
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
// Get the current reading position, in bytes from the
|
|
// beginning of the file. If the next call to read() will
|
|
// read the first byte in the file, tellg() returns 0.
|
|
//--------------------------------------------------------
|
|
|
|
virtual Imf::Int64 tellg()
|
|
{
|
|
return Pos;
|
|
}
|
|
|
|
//-------------------------------------------
|
|
// Set the current reading position.
|
|
// After calling seekg(i), tellg() returns i.
|
|
//-------------------------------------------
|
|
|
|
virtual void seekg(Imf::Int64 pos)
|
|
{
|
|
Pos = pos;
|
|
}
|
|
|
|
private:
|
|
|
|
const char* Data;
|
|
int32 Size;
|
|
int64 Pos;
|
|
};
|
|
|
|
|
|
namespace
|
|
{
|
|
/////////////////////////////////////////
|
|
// 8 bit per channel source
|
|
float ToLinear(uint8 LDRValue)
|
|
{
|
|
return pow((float)(LDRValue) / 255.f, 2.2f);
|
|
}
|
|
|
|
void ExtractAndConvertChannel(const uint8*Src, uint32 SrcChannels, uint32 x, uint32 y, float* ChannelOUT)
|
|
{
|
|
for (uint32 i = 0; i < x*y; i++, Src += SrcChannels)
|
|
{
|
|
ChannelOUT[i] = ToLinear(*Src);
|
|
}
|
|
}
|
|
|
|
void ExtractAndConvertChannel(const uint8*Src, uint32 SrcChannels, uint32 x, uint32 y, FFloat16* ChannelOUT)
|
|
{
|
|
for (uint32 i = 0; i < x*y; i++, Src += SrcChannels)
|
|
{
|
|
ChannelOUT[i] = FFloat16(ToLinear(*Src));
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////
|
|
// 16 bit per channel source
|
|
void ExtractAndConvertChannel(const FFloat16*Src, uint32 SrcChannels, uint32 x, uint32 y, float* ChannelOUT)
|
|
{
|
|
for (uint32 i = 0; i < x*y; i++, Src += SrcChannels)
|
|
{
|
|
ChannelOUT[i] = Src->GetFloat();
|
|
}
|
|
}
|
|
|
|
void ExtractAndConvertChannel(const FFloat16*Src, uint32 SrcChannels, uint32 x, uint32 y, FFloat16* ChannelOUT)
|
|
{
|
|
for (uint32 i = 0; i < x*y; i++, Src += SrcChannels)
|
|
{
|
|
ChannelOUT[i] = *Src;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////
|
|
// 32 bit per channel source
|
|
void ExtractAndConvertChannel(const float* Src, uint32 SrcChannels, uint32 x, uint32 y, float* ChannelOUT)
|
|
{
|
|
for (uint32 i = 0; i < x*y; i++, Src += SrcChannels)
|
|
{
|
|
ChannelOUT[i] = *Src;
|
|
}
|
|
}
|
|
|
|
void ExtractAndConvertChannel(const float* Src, uint32 SrcChannels, uint32 x, uint32 y, FFloat16* ChannelOUT)
|
|
{
|
|
for (uint32 i = 0; i < x*y; i++, Src += SrcChannels)
|
|
{
|
|
ChannelOUT[i] = *Src;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////
|
|
int32 GetNumChannelsFromFormat(ERGBFormat::Type Format)
|
|
{
|
|
switch (Format)
|
|
{
|
|
case ERGBFormat::RGBA:
|
|
case ERGBFormat::BGRA:
|
|
return 4;
|
|
case ERGBFormat::Gray:
|
|
return 1;
|
|
}
|
|
checkNoEntry();
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
const char* FExrImageWrapper::GetRawChannelName(int ChannelIndex) const
|
|
{
|
|
const int32 MaxChannels = 4;
|
|
static const char* RGBAChannelNames[] = { "R", "G", "B", "A" };
|
|
static const char* BGRAChannelNames[] = { "B", "G", "R", "A" };
|
|
static const char* GrayChannelNames[] = { "G" };
|
|
check(ChannelIndex < MaxChannels);
|
|
|
|
const char** ChannelNames = BGRAChannelNames;
|
|
|
|
switch (RawFormat)
|
|
{
|
|
case ERGBFormat::RGBA:
|
|
{
|
|
ChannelNames = RGBAChannelNames;
|
|
}
|
|
break;
|
|
case ERGBFormat::BGRA:
|
|
{
|
|
ChannelNames = BGRAChannelNames;
|
|
}
|
|
break;
|
|
case ERGBFormat::Gray:
|
|
{
|
|
check(ChannelIndex < ARRAY_COUNT(GrayChannelNames));
|
|
ChannelNames = GrayChannelNames;
|
|
}
|
|
break;
|
|
default:
|
|
checkNoEntry();
|
|
}
|
|
return ChannelNames[ChannelIndex];
|
|
}
|
|
|
|
template <Imf::PixelType OutputFormat, typename sourcetype>
|
|
void FExrImageWrapper::WriteFrameBufferChannel(Imf::FrameBuffer& ImfFrameBuffer, const char* ChannelName, const sourcetype* SrcData, TArray<uint8>& ChannelBuffer)
|
|
{
|
|
const int32 OutputPixelSize = ((OutputFormat == Imf::HALF) ? 2 : 4);
|
|
ChannelBuffer.AddUninitialized(Width*Height*OutputPixelSize);
|
|
uint32 SrcChannels = GetNumChannelsFromFormat(RawFormat);
|
|
switch (OutputFormat)
|
|
{
|
|
case Imf::HALF:
|
|
{
|
|
ExtractAndConvertChannel(SrcData, SrcChannels, Width, Height, (FFloat16*)&ChannelBuffer[0]);
|
|
}
|
|
break;
|
|
case Imf::FLOAT:
|
|
{
|
|
ExtractAndConvertChannel(SrcData, SrcChannels, Width, Height, (float*)&ChannelBuffer[0]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
Imf::Slice FrameChannel = Imf::Slice(OutputFormat, (char*)&ChannelBuffer[0], OutputPixelSize, Width*OutputPixelSize);
|
|
ImfFrameBuffer.insert(ChannelName, FrameChannel);
|
|
}
|
|
|
|
template <Imf::PixelType OutputFormat, typename sourcetype>
|
|
void FExrImageWrapper::CompressRaw(const sourcetype* SrcData, bool bIgnoreAlpha)
|
|
{
|
|
const double StartTime = FPlatformTime::Seconds();
|
|
uint32 NumWriteComponents = GetNumChannelsFromFormat(RawFormat);
|
|
if (bIgnoreAlpha && NumWriteComponents == 4)
|
|
{
|
|
NumWriteComponents = 3;
|
|
}
|
|
|
|
Imf::Header Header(Width, Height);
|
|
|
|
for (uint32 Channel = 0; Channel < NumWriteComponents; Channel++)
|
|
{
|
|
Header.channels().insert(GetRawChannelName(Channel), Imf::Channel(OutputFormat));
|
|
}
|
|
|
|
FMemFileOut MemFile("");
|
|
const int32 OutputPixelSize = ((OutputFormat == Imf::HALF) ? 2 : 4);
|
|
MemFile.Data.AddUninitialized(Width * Height * NumWriteComponents * OutputPixelSize);
|
|
|
|
Imf::FrameBuffer ImfFrameBuffer;
|
|
TArray<uint8> ChannelOutputBuffers[4];
|
|
|
|
for (uint32 Channel = 0; Channel < NumWriteComponents; Channel++)
|
|
{
|
|
WriteFrameBufferChannel<OutputFormat>(ImfFrameBuffer, GetRawChannelName(Channel), SrcData + Channel, ChannelOutputBuffers[Channel]);
|
|
}
|
|
|
|
Imf::OutputFile ImfFile(MemFile, Header);
|
|
ImfFile.setFrameBuffer(ImfFrameBuffer);
|
|
ImfFile.writePixels(Height);
|
|
|
|
CompressedData.AddUninitialized(MemFile.tellp());
|
|
FMemory::Memcpy(CompressedData.GetData(), MemFile.Data.GetData(), MemFile.tellp());
|
|
|
|
const double DeltaTime = FPlatformTime::Seconds() - StartTime;
|
|
UE_LOG(LogImageWrapper, Verbose, TEXT("Compressed image in %.3f seconds"), DeltaTime);
|
|
}
|
|
|
|
void FExrImageWrapper::Compress( int32 Quality )
|
|
{
|
|
check(RawData.Num() != 0);
|
|
check(Width > 0);
|
|
check(Height > 0);
|
|
check(RawBitDepth == 8 || RawBitDepth == 16 || RawBitDepth == 32);
|
|
|
|
const int32 MaxComponents = 4;
|
|
|
|
switch (RawBitDepth)
|
|
{
|
|
case 8:
|
|
CompressRaw<Imf::HALF>(&RawData[0], false);
|
|
break;
|
|
case 16:
|
|
CompressRaw<Imf::HALF>((const FFloat16*)&RawData[0], false);
|
|
break;
|
|
case 32:
|
|
CompressRaw<Imf::FLOAT>((const float*)&RawData[0], false);
|
|
break;
|
|
default:
|
|
checkNoEntry();
|
|
}
|
|
}
|
|
|
|
void FExrImageWrapper::Uncompress( const ERGBFormat::Type InFormat, const int32 InBitDepth )
|
|
{
|
|
// Ensure we haven't already uncompressed the file.
|
|
if ( RawData.Num() != 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
FMemFileIn MemFile(&CompressedData[0], CompressedData.Num());
|
|
|
|
Imf::RgbaInputFile ImfFile(MemFile);
|
|
|
|
Imath::Box2i win = ImfFile.dataWindow();
|
|
|
|
check(BitDepth == 16);
|
|
check(Width);
|
|
check(Height);
|
|
|
|
uint32 Channels = 4;
|
|
|
|
RawData.Empty();
|
|
RawData.AddUninitialized( Width * Height * Channels * (BitDepth / 8) );
|
|
|
|
int dx = win.min.x;
|
|
int dy = win.min.y;
|
|
|
|
ImfFile.setFrameBuffer((Imf::Rgba*)&RawData[0] - dx - dy * Width, 1, Width);
|
|
ImfFile.readPixels(win.min.y, win.max.y);
|
|
}
|
|
|
|
// from http://www.openexr.com/ReadingAndWritingImageFiles.pdf
|
|
bool IsThisAnOpenExrFile(Imf::IStream& f)
|
|
{
|
|
char b[4];
|
|
f.read(b, sizeof(b));
|
|
|
|
f.seekg(0);
|
|
|
|
return b[0] == 0x76 && b[1] == 0x2f && b[2] == 0x31 && b[3] == 0x01;
|
|
}
|
|
|
|
bool FExrImageWrapper::SetCompressed( const void* InCompressedData, int32 InCompressedSize )
|
|
{
|
|
if(!FImageWrapperBase::SetCompressed( InCompressedData, InCompressedSize))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FMemFileIn MemFile(InCompressedData, InCompressedSize);
|
|
|
|
if(!IsThisAnOpenExrFile(MemFile))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Imf::RgbaInputFile ImfFile(MemFile);
|
|
|
|
Imath::Box2i win = ImfFile.dataWindow();
|
|
|
|
Imath::V2i dim(win.max.x - win.min.x + 1, win.max.y - win.min.y + 1);
|
|
|
|
BitDepth = 16;
|
|
|
|
Width = dim.x;
|
|
Height = dim.y;
|
|
|
|
// ideally we can specify float here
|
|
Format = ERGBFormat::RGBA;
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif // WITH_UNREALEXR
|