Files
UnrealEngine/Engine/Plugins/Media/ImgMedia/Source/OpenExrWrapper/Private/OpenExrWrapper.cpp
2025-05-18 13:04:45 +08:00

704 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "OpenExrWrapper.h"
#include <exception>
#include "Containers/UnrealString.h"
#include "Logging/LogMacros.h"
#include "Math/IntRect.h"
#include "Modules/ModuleManager.h"
PRAGMA_DEFAULT_VISIBILITY_START
THIRD_PARTY_INCLUDES_START
#include "Imath/ImathBox.h"
#include "OpenEXR/ImfChannelList.h"
#include "OpenEXR/ImfCompressionAttribute.h"
#include "OpenEXR/ImfHeader.h"
#include "OpenEXR/ImfIntAttribute.h"
#include "OpenEXR/ImfOutputFile.h"
#include "OpenEXR/ImfTileDescriptionAttribute.h"
#include "OpenEXR/ImfRgbaFile.h"
#include "OpenEXR/ImfStandardAttributes.h"
#include "OpenEXR/ImfTiledInputFile.h"
#include "OpenEXR/ImfTiledOutputFile.h"
#include "OpenEXR/ImfTiledRgbaFile.h"
THIRD_PARTY_INCLUDES_END
PRAGMA_DEFAULT_VISIBILITY_END
DECLARE_LOG_CATEGORY_EXTERN(LogOpenEXRWrapper, Log, All);
DEFINE_LOG_CATEGORY(LogOpenEXRWrapper);
/* FOpenExr
*****************************************************************************/
void FOpenExr::SetGlobalThreadCount(uint16 ThreadCount)
{
Imf::setGlobalThreadCount(ThreadCount);
}
/* FRgbaInputFile
*****************************************************************************/
FRgbaInputFile::FRgbaInputFile(const FString& FilePath)
{
try
{
InputFile = new Imf::RgbaInputFile(TCHAR_TO_ANSI(*FilePath));
}
catch (std::exception const& Exception)
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Cannot load EXR file: %s"), StringCast<TCHAR>(Exception.what()).Get());
InputFile = nullptr;
}
}
FRgbaInputFile::FRgbaInputFile(const FString& FilePath, uint16 ThreadCount)
{
try
{
InputFile = new Imf::RgbaInputFile(TCHAR_TO_ANSI(*FilePath), ThreadCount);
}
catch (std::exception const& Exception)
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Cannot load EXR file: %s"), StringCast<TCHAR>(Exception.what()).Get());
InputFile = nullptr;
}
}
FRgbaInputFile::~FRgbaInputFile()
{
delete (Imf::RgbaInputFile*)InputFile;
}
const TCHAR* FRgbaInputFile::GetCompressionName() const
{
auto CompressionAttribute = ((Imf::RgbaInputFile*)InputFile)->header().findTypedAttribute<Imf::CompressionAttribute>("compression");
if (CompressionAttribute == nullptr)
{
return TEXT("");
}
Imf::Compression Compression = CompressionAttribute->value();
switch (Compression)
{
case Imf::NO_COMPRESSION:
return TEXT("Uncompressed");
case Imf::RLE_COMPRESSION:
return TEXT("RLE");
case Imf::ZIPS_COMPRESSION:
return TEXT("ZIPS");
case Imf::ZIP_COMPRESSION:
return TEXT("ZIP");
case Imf::PIZ_COMPRESSION:
return TEXT("PIZ");
case Imf::PXR24_COMPRESSION:
return TEXT("PXR24");
case Imf::B44_COMPRESSION:
return TEXT("B44");
case Imf::B44A_COMPRESSION:
return TEXT("B44A");
default:
return TEXT("Unknown");
}
}
FIntPoint FRgbaInputFile::GetDataWindow() const
{
Imath::Box2i Win = ((Imf::RgbaInputFile*)InputFile)->dataWindow();
return FIntPoint(
Win.max.x - Win.min.x + 1,
Win.max.y - Win.min.y + 1
);
}
FIntRect FRgbaInputFile::GetDataWindowRect() const
{
Imath::Box2i Win;
if (InputFile != nullptr)
{
Win = ((Imf::RgbaInputFile*)InputFile)->dataWindow();
}
return FIntRect(Win.min.x, Win.min.y, Win.max.x, Win.max.y);
}
FIntRect FRgbaInputFile::GetDisplayWindow() const
{
Imath::Box2i Win;
if (InputFile != nullptr)
{
Win = ((Imf::RgbaInputFile*)InputFile)->displayWindow();
}
return FIntRect(Win.min.x, Win.min.y, Win.max.x, Win.max.y);
}
FFrameRate FRgbaInputFile::GetFrameRate(const FFrameRate& DefaultValue) const
{
auto Attribute = ((Imf::RgbaInputFile*)InputFile)->header().findTypedAttribute<Imf::RationalAttribute>("framesPerSecond");
if (Attribute == nullptr)
{
return DefaultValue;
}
const Imf::Rational& Value = Attribute->value();
return FFrameRate(Value.n, Value.d);
}
int32 FRgbaInputFile::GetNumChannels() const
{
if (InputFile == nullptr)
{
return 0;
}
Imf::RgbaChannels Channels = ((Imf::RgbaInputFile*)InputFile)->channels();
int32 NumChannels = 3;
switch (Channels)
{
case Imf::RgbaChannels::WRITE_R:
case Imf::RgbaChannels::WRITE_G:
case Imf::RgbaChannels::WRITE_B:
case Imf::RgbaChannels::WRITE_A:
case Imf::RgbaChannels::WRITE_Y:
case Imf::RgbaChannels::WRITE_C:
NumChannels = 1;
break;
case Imf::RgbaChannels::WRITE_YC:
case Imf::RgbaChannels::WRITE_YA:
NumChannels = 2;
break;
case Imf::RgbaChannels::WRITE_RGB:
case Imf::RgbaChannels::WRITE_YCA:
NumChannels = 3;
break;
case Imf::RgbaChannels::WRITE_RGBA:
NumChannels = 4;
break;
default:
break;
}
return NumChannels;
}
bool FRgbaInputFile::GetTileSize(FIntPoint& OutTileSize) const
{
const Imf::TileDescriptionAttribute* TileDescAttr = ((Imf::RgbaInputFile*)InputFile)->header().findTypedAttribute<Imf::TileDescriptionAttribute>("tiles");
if (TileDescAttr)
{
Imf::TileDescription TileDesc = TileDescAttr->value();
OutTileSize = FIntPoint(TileDesc.xSize, TileDesc.ySize);
}
return TileDescAttr != nullptr;
}
int32 FRgbaInputFile::GetUncompressedSize() const
{
const int32 NumChannels = GetNumChannels();
const int32 ChannelSize = sizeof(int16);
const FIntPoint Window = GetDataWindow();
return (Window.X * Window.Y * NumChannels * ChannelSize);
}
bool FRgbaInputFile::IsComplete() const
{
return ((Imf::RgbaInputFile*)InputFile)->isComplete();
}
bool FRgbaInputFile::HasInputFile() const
{
return InputFile != nullptr;
}
void FRgbaInputFile::ReadPixels(int32 StartY, int32 EndY)
{
try
{
// Since we convert everything to a coordinate system that goes from 0 to infinity when we GetDataWindow()
// we need to convert it back to original coordinate system.
Imath::Box2i Win = ((Imf::RgbaInputFile*)InputFile)->dataWindow();
((Imf::RgbaInputFile*)InputFile)->readPixels(StartY + Win.min.y, EndY + Win.min.y);
}
catch (std::exception const& Exception)
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Cannot read EXR file: %s (%s)"),
ANSI_TO_TCHAR(((Imf::RgbaInputFile*)InputFile)->fileName()),
StringCast<TCHAR>(Exception.what()).Get());
}
}
void FRgbaInputFile::SetFrameBuffer(void* Buffer, const FIntPoint& BufferDim)
{
Imath::Box2i Win = ((Imf::RgbaInputFile*)InputFile)->dataWindow();
((Imf::RgbaInputFile*)InputFile)->setFrameBuffer((Imf::Rgba*)Buffer - Win.min.x - Win.min.y * BufferDim.X, 1, BufferDim.X);
}
bool FRgbaInputFile::GetIntAttribute(const FString& Name, int32& Value)
{
bool bIsAttributeFound = false;
if (InputFile != nullptr)
{
const Imf::IntAttribute* Attribute =
((Imf::RgbaInputFile*)InputFile)->header().
findTypedAttribute<Imf::IntAttribute>(std::string(TCHAR_TO_ANSI(*Name)));
if (Attribute != nullptr)
{
Value = Attribute->value();
bIsAttributeFound = true;
}
}
return bIsAttributeFound;
}
FBaseOutputFile::FBaseOutputFile(
const FIntPoint& DisplayWindowMin,
const FIntPoint& DisplayWindowMax,
const FIntPoint& DataWindowMin,
const FIntPoint& DataWindowMax)
{
OutputFile = nullptr;
Imath::Box2i EXRDisplayWindow = Imath::Box2i(Imath::V2i(DisplayWindowMin.X, DisplayWindowMin.Y),
Imath::V2i(DisplayWindowMax.X, DisplayWindowMax.Y));
Imath::Box2i EXRDataWindow = Imath::Box2i(Imath::V2i(DataWindowMin.X, DataWindowMin.Y),
Imath::V2i(DataWindowMax.X, DataWindowMax.Y));
Header = new Imf::Header(EXRDisplayWindow, EXRDataWindow, 1, IMATH_NAMESPACE::V2f(0, 0), 1, Imf::INCREASING_Y,
Imf::NO_COMPRESSION);
}
FBaseOutputFile::~FBaseOutputFile()
{
if (Header != nullptr)
{
delete (Imf::Header*)Header;
}
if (OutputFile != nullptr)
{
delete (Imf::TiledRgbaOutputFile*)OutputFile;
}
}
void FBaseOutputFile::AddIntAttribute(const FString& Name, int32 Value)
{
// Make sure we don't have an output file yet.
if (OutputFile == nullptr)
{
((Imf::Header*)Header)->insert(std::string(StringCast<ANSICHAR>(*Name).Get()), Imf::IntAttribute(Value));
}
else
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Attribute %s added after calling CreateOutputFile."),
*Name);
}
}
FTiledRgbaOutputFile::FTiledRgbaOutputFile(
const FIntPoint& DisplayWindowMin,
const FIntPoint& DisplayWindowMax,
const FIntPoint& DataWindowMin,
const FIntPoint& DataWindowMax)
: FBaseOutputFile(DisplayWindowMin, DisplayWindowMax, DataWindowMin, DataWindowMax)
{
}
void FTiledRgbaOutputFile::CreateOutputFile(const FString& FilePath,
int32 TileWidth, int32 TileHeight, int32 NumChannels, bool bIsMipsEnabled)
{
if (OutputFile == nullptr)
{
try
{
// Get channels.
Imf::RgbaChannels Channels;
switch (NumChannels)
{
case 1:
Channels = Imf::WRITE_R;
break;
case 2:
Channels = Imf::WRITE_YC;
break;
case 3:
Channels = Imf::WRITE_RGB;
break;
case 4:
Channels = Imf::WRITE_RGBA;
break;
default:
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Unsupported number of channels %d"),
NumChannels);
Channels = Imf::WRITE_RGBA;
break;
}
// Create output file.
OutputFile = new Imf::TiledRgbaOutputFile(StringCast<ANSICHAR>(*FilePath).Get(),
*((Imf::Header*)Header),
Channels,
TileWidth, TileHeight,
bIsMipsEnabled ? Imf::MIPMAP_LEVELS : Imf::ONE_LEVEL,
Imf::ROUND_DOWN);
}
catch (std::exception const& Exception)
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Cannot write EXR file: %s (%s)"),
*FilePath, StringCast<TCHAR>(Exception.what()).Get());
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("Cannot create output file as it has already been created."));
}
}
int32 FTiledRgbaOutputFile::GetNumberOfMipLevels()
{
if (OutputFile != nullptr)
{
return ((Imf::TiledRgbaOutputFile*)OutputFile)->numLevels();
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("GetNumberOfMipLevels failed: CreateOutputFile has not been called yet."));
return 0;
}
}
void FTiledRgbaOutputFile::SetFrameBuffer(void* Buffer, const FIntPoint& Stride)
{
if (OutputFile != nullptr)
{
((Imf::TiledRgbaOutputFile*)OutputFile)->setFrameBuffer((Imf::Rgba*)Buffer, Stride.X, Stride.Y);
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("SetFrameBuffer failed: CreateOutputFile has not been called yet."));
}
}
void FTiledRgbaOutputFile::WriteTile(int32 TileX, int32 TileY, int32 MipLevel)
{
if (OutputFile != nullptr)
{
try
{
((Imf::TiledRgbaOutputFile*)OutputFile)->writeTile(TileX, TileY, MipLevel);
}
catch (std::exception const& Exception)
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Cannot write EXR file: %s"),
StringCast<TCHAR>(Exception.what()).Get());
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("WriteTile failed: CreateOutputFile has not been called yet."));
}
}
FTiledOutputFile::FTiledOutputFile(
const FIntPoint& DisplayWindowMin,
const FIntPoint& DisplayWindowMax,
const FIntPoint& DataWindowMin,
const FIntPoint& DataWindowMax,
bool bInIsTiled)
: FBaseOutputFile(DisplayWindowMin, DisplayWindowMax, DataWindowMin, DataWindowMax)
, bIsTiled(bInIsTiled)
{
FrameBuffer = new Imf::FrameBuffer;
}
FTiledOutputFile::~FTiledOutputFile()
{
if (FrameBuffer != nullptr)
{
delete (Imf::FrameBuffer*)FrameBuffer;
}
}
void FTiledOutputFile::AddChannel(const FString& Name)
{
((Imf::Header*)Header)->channels().insert(StringCast<ANSICHAR>(*Name).Get(), Imf::Channel(Imf::HALF));
}
void FTiledOutputFile::CreateOutputFile(const FString& FilePath,
int32 TileWidth, int32 TileHeight, bool bIsMipsEnabled, int32 NumThreads)
{
if (OutputFile == nullptr)
{
try
{
if (bIsTiled)
{
((Imf::Header*)Header)->setTileDescription(Imf::TileDescription(TileWidth, TileHeight,
bIsMipsEnabled ? Imf::MIPMAP_LEVELS : Imf::ONE_LEVEL));
// Create output file.
OutputFile = new Imf::TiledOutputFile(StringCast<ANSICHAR>(*FilePath).Get(),
*((Imf::Header*)Header), NumThreads);
}
else
{
// Create output file.
OutputFile = new Imf::OutputFile(StringCast<ANSICHAR>(*FilePath).Get(),
*((Imf::Header*)Header), NumThreads);
}
}
catch (std::exception const& Exception)
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Cannot write EXR file: %s (%s)"),
*FilePath, StringCast<TCHAR>(Exception.what()).Get());
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("Cannot create output file as it has already been created."));
}
}
void FTiledOutputFile::AddFrameBufferChannel(const FString& Name, void* Base,
const FIntPoint& Stride)
{
((Imf::FrameBuffer*)FrameBuffer)->insert(StringCast<ANSICHAR>(*Name).Get(),
Imf::Slice(Imf::HALF, (char*)Base, Stride.X, Stride.Y));
}
void FTiledOutputFile::UpdateFrameBufferChannel(const FString& Name, void* Base,
const FIntPoint& Stride)
{
Imf::Slice* Slice = ((Imf::FrameBuffer*)FrameBuffer)->findSlice(
StringCast<ANSICHAR>(*Name).Get());
if (Slice != nullptr)
{
Slice->base = (char*)Base;
Slice->xStride = Stride.X;
Slice->yStride = Stride.Y;
}
else
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Could not find frame buffer channel %s."), *Name);
}
}
void FTiledOutputFile::SetFrameBuffer()
{
if (OutputFile != nullptr)
{
if (bIsTiled)
{
((Imf::TiledOutputFile*)OutputFile)->setFrameBuffer(*((Imf::FrameBuffer*)FrameBuffer));
}
else
{
((Imf::OutputFile*)OutputFile)->setFrameBuffer(*((Imf::FrameBuffer*)FrameBuffer));
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("Cannot set frame buffer as there is no output file."));
}
}
void FTiledOutputFile::WriteTile(int32 TileX, int32 TileY, int32 MipLevel)
{
if (bIsTiled)
{
if (OutputFile != nullptr)
{
try
{
((Imf::TiledOutputFile*)OutputFile)->writeTiles(0, TileX, 0, TileY, MipLevel);
}
catch (std::exception const& Exception)
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Cannot write EXR file: %s"),
StringCast<TCHAR>(Exception.what()).Get());
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("WriteTile failed: CreateOutputFile has not been called yet."));
}
}
else
{
WriteTiles(0, 0, 0, 0, 0);
}
}
void FTiledOutputFile::WriteTiles(int32 TileX1, int32 TileX2, int32 TileY1, int32 TileY2, int32 MipLevel)
{
if (OutputFile != nullptr)
{
try
{
if (bIsTiled)
{
((Imf::TiledOutputFile*)OutputFile)->writeTiles(TileX1, TileX2, TileY1, TileY2, MipLevel);
}
else
{
Imath::Box2i DataWindow = ((Imf::Header*)Header)->dataWindow();
((Imf::OutputFile*)OutputFile)->writePixels(DataWindow.max.y - DataWindow.min.y + 1);
}
}
catch (std::exception const& Exception)
{
UE_LOG(LogOpenEXRWrapper, Error, TEXT("Cannot write EXR file: %s"),
StringCast<TCHAR>(Exception.what()).Get());
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("WriteTiles failed: CreateOutputFile has not been called yet."));
}
}
int32 FTiledOutputFile::GetNumberOfMipLevels()
{
if (OutputFile != nullptr)
{
if (bIsTiled)
{
return ((Imf::TiledOutputFile*)OutputFile)->numLevels();
}
else
{
return 1;
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("GetNumberOfMipLevels failed: CreateOutputFile has not been called yet."));
return 0;
}
}
int32 FTiledOutputFile::GetMipWidth(int32 MipLevel)
{
if (OutputFile != nullptr)
{
if (bIsTiled)
{
return ((Imf::TiledOutputFile*)OutputFile)->levelWidth(MipLevel);
}
else
{
Imath::Box2i DataWindow = ((Imf::Header*)Header)->dataWindow();
return DataWindow.size().x + 1;
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("GetMipWidth failed: CreateOutputFile has not been called yet."));
return 0;
}
}
int32 FTiledOutputFile::GetMipHeight(int32 MipLevel)
{
if (OutputFile != nullptr)
{
if (bIsTiled)
{
return ((Imf::TiledOutputFile*)OutputFile)->levelHeight(MipLevel);
}
else
{
Imath::Box2i DataWindow = ((Imf::Header*)Header)->dataWindow();
return DataWindow.size().y + 1;
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("GetMipHeight failed: CreateOutputFile has not been called yet."));
return 0;
}
}
int32 FTiledOutputFile::GetNumXTiles(int32 MipLevel)
{
if (OutputFile != nullptr)
{
if (bIsTiled)
{
return ((Imf::TiledOutputFile*)OutputFile)->numXTiles(MipLevel);
}
else
{
return 0;
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("GetNumXTiles failed: CreateOutputFile has not been called yet."));
return 0;
}
}
int32 FTiledOutputFile::GetNumYTiles(int32 MipLevel)
{
if (OutputFile != nullptr)
{
if (bIsTiled)
{
return ((Imf::TiledOutputFile*)OutputFile)->numYTiles(MipLevel);
}
else
{
return 0;
}
}
else
{
UE_LOG(LogOpenEXRWrapper, Error,
TEXT("GetNumYTiles failed: CreateOutputFile has not been called yet."));
return 0;
}
}
IMPLEMENT_MODULE(FDefaultModuleImpl, OpenExrWrapper);