Merging //UE4/Release-4.11 to //UE4/Main (up to CL#2852902)

==========================
MAJOR FEATURES + CHANGES
==========================

Change 2835191 on 2016/01/19 by Nick.Whiting

	Invert the y-axis on the SteamVR controllers to match the convention of the engine and the rest of the gamepads

	#jira UE-22705

Change 2835686 on 2016/01/20 by Gareth.Martin

	Fixed landscape material instances not being updated if holes are painted on a landscape that doesn't have the landscape visibility mask node in the material and then the visibility mask node is added to the material later.
	#jira UE-18187

Change 2835767 on 2016/01/20 by Richard.Hinckley

	#jira UE-25499 Added a cursor to TopDown template (C++ version) to match the BP version.

Change 2835772 on 2016/01/20 by Richard.Hinckley

	#jira UE-25499 Adding the material asset for the C++ TopDown template's cursor.

Change 2835811 on 2016/01/20 by Taizyd.Korambayil

	#jira UE-25699 Added Validity Checks in BP logic, unchecked CDO for Pixel Ship, to Fix Log Warnings
	#jira UE-25704 Adjusted Matinee to happen at Box Location
	#jira UE-25688 Adjusted Player Starts
	#jira UE-25693 Adjusted Player Starts

Change 2835863 on 2016/01/20 by Gareth.Martin

	Fixed crash in the landscape ramp and mirror tools if the streaming level containing the landscape is hidden (or possibly if the landscape actor is deleted)
	#jira UE-24883

Change 2835889 on 2016/01/20 by Taizyd.Korambayil

	#jira UE-25698 Enabled V-sync, also fixed up player Respawn Issue

Change 2835995 on 2016/01/20 by Jamie.Dale

	The output log now hard-wraps lines to prevent long lines causing performance issues

	#jira UE-24187

Change 2836052 on 2016/01/20 by Taizyd.Korambayil

	#jira UE-25675 Added Blocking Volume to prevent Player from Falling off map
	#jira UE-25676 Added Blocking Volumes so that the Player doesn't get stucl at awkward corners under the Bridge

Change 2836137 on 2016/01/20 by Chad.Taylor

	Vehicle and VehicleAdv template content fixes for new VR camera

	#jira UE-25507

Change 2836166 on 2016/01/20 by Gareth.Martin

	Fixed hiding a streaming level containing a landscape causing the landscape editor to switch to the "New Landscape" tool instead of exiting
	#jira UE-25093

Change 2836174 on 2016/01/20 by Chad.Taylor

	IHeadMountedDisplay crash fix associated with accessing a dangling pointer.

	#jira UE-25272

Change 2836179 on 2016/01/20 by Jamie.Dale

	Optimized FShapedGlyphSequence reverse look-up

	There's now a reverse look-up map of cluster indices to their glyph data in order to avoid brute force looping

	#jira UE-24187

Change 2836286 on 2016/01/20 by Chris.Babcock

	Update Qualcomm TextureConverter for OSX
	#jira UE-22092
	#ue4
	#android

Change 2836328 on 2016/01/20 by Nick.Darnell

	Fixing a problem with widget components crashing on destruction with the render commands to pre/post render for window render commands needing access to the policy, but it potentially being deleted.  Inserting a NoOp command that keeps the shared ptr alive through the RHI render process.

	#jira UE-25752

Change 2836342 on 2016/01/20 by Nick.Darnell

	Depending on shutdown order, the Slate Renderer may go away, and then render data handles may not be collected correctly because they are trying to reference a pointer that's no longer valid and cause a crash on exit. The correct approach would be to have render handles actually have a pointer back to who owns them, in this case the RHI Resource Manager, which is still alive and well at this point in the pipeline.  Then if the resource manager is collected, it forces all handles to get cleaned up correctly, or if the handles are collected first, they can be sure they've got a valid pointer back to the resource manager.

	#jira UE-25753

Change 2836358 on 2016/01/20 by Taizyd.Korambayil

	#jira UE-25710 Replaced Deprecated Nodes

Change 2836510 on 2016/01/20 by Taizyd.Korambayil

	#jira UE-25718 Adjsuted BP to make pointer decal rotate in the direction of surface

Change 2836564 on 2016/01/20 by Taizyd.Korambayil

	#jira UE-25716 Added bool to store last Moved Direction

Change 2836697 on 2016/01/20 by Taizyd.Korambayil

	#jira UE-25740 Removed unused VR Nodes to remove Log errors on Mac

Change 2836725 on 2016/01/20 by Peter.Sauerbrei

	workaround for thread race when trying to release the TargetDeviceService endpoint after an unclaim message is sent
	#jira UE-25123

Change 2836782 on 2016/01/20 by Jamie.Dale

	Added FTextLayout::AddLines

	This is similar to AddLine, however it allows you to add multiple lines in a single call, thus avoiding the re-justification cost associated with each call to AddLine.

	AddLine has also been changed to take the same structure type as AddLines (which takes an array of these structures), and the existing version of AddLine has been deprecated.

	#jira UE-24187

Change 2836801 on 2016/01/20 by Jeff.Campeau

[CL 2857187 by Matthew Griffin in Main branch]
This commit is contained in:
Matthew Griffin
2016-02-05 11:54:00 -05:00
committed by Matthew.Griffin@epicgames.com
parent 9d288e4107
commit 755f725131
319 changed files with 6415 additions and 3649 deletions
@@ -0,0 +1,238 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ImageWrapperPrivatePCH.h"
#include "BmpImageSupport.h"
/**
* BMP image wrapper class.
* This code was adapted from UTextureFactory::ImportTexture, but has not been throughly tested.
*/
FBmpImageWrapper::FBmpImageWrapper(bool bInHasHeader, bool bInHalfHeight)
: FImageWrapperBase()
, bHasHeader(bInHasHeader)
, bHalfHeight(bInHalfHeight)
{
}
void FBmpImageWrapper::Compress( int32 Quality )
{
checkf(false, TEXT("BMP compression not supported"));
}
void FBmpImageWrapper::Uncompress( const ERGBFormat::Type InFormat, const int32 InBitDepth )
{
const uint8* Buffer = CompressedData.GetData();
if( !bHasHeader || ((CompressedData.Num()>=sizeof(FBitmapFileHeader)+sizeof(FBitmapInfoHeader)) && Buffer[0]=='B' && Buffer[1]=='M') )
{
UncompressBMPData(InFormat, InBitDepth);
}
}
void FBmpImageWrapper::UncompressBMPData( const ERGBFormat::Type InFormat, const int32 InBitDepth )
{
const uint8* Buffer = CompressedData.GetData();
const FBitmapInfoHeader* bmhdr = NULL;
const uint8* Bits = NULL;
if(bHasHeader)
{
bmhdr = (FBitmapInfoHeader *)(Buffer + sizeof(FBitmapFileHeader));
Bits = Buffer + ((FBitmapFileHeader *)Buffer)->bfOffBits;
}
else
{
bmhdr = (FBitmapInfoHeader *)Buffer;
Bits = Buffer + sizeof(FBitmapInfoHeader);
}
if( bmhdr->biCompression != BCBI_RGB )
{
UE_LOG(LogImageWrapper, Error, TEXT("RLE compression of BMP images not supported") );
return;
}
if( bmhdr->biPlanes==1 && bmhdr->biBitCount==8 )
{
// Do palette.
const uint8* bmpal = (uint8*)CompressedData.GetData() + sizeof(FBitmapFileHeader) + sizeof(FBitmapInfoHeader);
// Set texture properties.
Width = bmhdr->biWidth;
const bool bNegativeHeight = (bmhdr->biHeight < 0);
Height = FMath::Abs(bHalfHeight ? bmhdr->biHeight / 2 : bmhdr->biHeight);
Format = ERGBFormat::BGRA;
RawData.Empty(Height * Width * 4);
RawData.AddUninitialized(Height * Width * 4);
FColor* ImageData = (FColor*)RawData.GetData();
// If the number for color palette entries is 0, we need to default to 2^biBitCount entries. In this case 2^8 = 256
int32 clrPaletteCount = bmhdr->biClrUsed ? bmhdr->biClrUsed : 256;
TArray<FColor> Palette;
for( int32 i=0; i<clrPaletteCount; i++ )
Palette.Add(FColor( bmpal[i*4+2], bmpal[i*4+1], bmpal[i*4+0], 255 ));
while( Palette.Num()<256 )
Palette.Add(FColor(0,0,0,255));
// Copy scanlines, accounting for scanline direction according to the Height field.
const int32 SrcStride = Align(Width, 4);
const int32 SrcPtrDiff = bNegativeHeight ? SrcStride : -SrcStride;
const uint8* SrcPtr = Bits + (bNegativeHeight ? 0 : Height - 1) * SrcStride;
for (int32 Y = 0; Y < Height; Y++)
{
for (int32 X = 0; X < Width; X++)
{
*ImageData++ = Palette[SrcPtr[X]];
}
SrcPtr += SrcPtrDiff;
}
}
else if( bmhdr->biPlanes==1 && bmhdr->biBitCount==24 )
{
// Set texture properties.
Width = bmhdr->biWidth;
const bool bNegativeHeight = (bmhdr->biHeight < 0);
Height = FMath::Abs(bHalfHeight ? bmhdr->biHeight / 2 : bmhdr->biHeight);
Format = ERGBFormat::BGRA;
RawData.Empty(Height * Width * 4);
RawData.AddUninitialized(Height * Width * 4);
uint8* ImageData = RawData.GetData();
// Copy scanlines, accounting for scanline direction according to the Height field.
const int32 SrcStride = Align(Width * 3, 4);
const int32 SrcPtrDiff = bNegativeHeight ? SrcStride : -SrcStride;
const uint8* SrcPtr = Bits + (bNegativeHeight ? 0 : Height - 1) * SrcStride;
for (int32 Y = 0; Y < Height; Y++)
{
const uint8* SrcRowPtr = SrcPtr;
for (int32 X = 0; X < Width; X++)
{
*ImageData++ = *SrcRowPtr++;
*ImageData++ = *SrcRowPtr++;
*ImageData++ = *SrcRowPtr++;
*ImageData++ = 0xFF;
}
SrcPtr += SrcPtrDiff;
}
}
else if( bmhdr->biPlanes==1 && bmhdr->biBitCount==32 )
{
// Set texture properties.
Width = bmhdr->biWidth;
const bool bNegativeHeight = (bmhdr->biHeight < 0);
Height = FMath::Abs(bHalfHeight ? bmhdr->biHeight / 2 : bmhdr->biHeight);
Format = ERGBFormat::BGRA;
RawData.Empty(Height * Width * 4);
RawData.AddUninitialized(Height * Width * 4);
uint8* ImageData = RawData.GetData();
// Copy scanlines, accounting for scanline direction according to the Height field.
const int32 SrcStride = Width * 4;
const int32 SrcPtrDiff = bNegativeHeight ? SrcStride : -SrcStride;
const uint8* SrcPtr = Bits + (bNegativeHeight ? 0 : Height - 1) * SrcStride;
for (int32 Y = 0; Y < Height; Y++)
{
const uint8* SrcRowPtr = SrcPtr;
for (int32 X = 0; X < Width; X++)
{
*ImageData++ = *SrcRowPtr++;
*ImageData++ = *SrcRowPtr++;
*ImageData++ = *SrcRowPtr++;
*ImageData++ = *SrcRowPtr++;
}
SrcPtr += SrcPtrDiff;
}
}
else if( bmhdr->biPlanes==1 && bmhdr->biBitCount==16 )
{
UE_LOG(LogImageWrapper, Error, TEXT("BMP 16 bit format no longer supported. Use terrain tools for importing/exporting heightmaps.") );
}
else
{
UE_LOG(LogImageWrapper, Error, TEXT("BMP uses an unsupported format (%i/%i)"), bmhdr->biPlanes, bmhdr->biBitCount );
}
}
bool FBmpImageWrapper::SetCompressed( const void* InCompressedData, int32 InCompressedSize )
{
bool bResult = FImageWrapperBase::SetCompressed( InCompressedData, InCompressedSize );
return bResult && (bHasHeader ? LoadBMPHeader() : LoadBMPInfoHeader()); // Fetch the variables from the header info
}
bool FBmpImageWrapper::LoadBMPHeader()
{
const FBitmapInfoHeader* bmhdr = (FBitmapInfoHeader *)(CompressedData.GetData() + sizeof(FBitmapFileHeader));
const FBitmapFileHeader* bmf = (FBitmapFileHeader *)(CompressedData.GetData() + 0);
if( (CompressedData.Num() >= sizeof(FBitmapFileHeader) + sizeof(FBitmapInfoHeader)) && CompressedData.GetData()[0] == 'B' && CompressedData.GetData()[1] == 'M' )
{
if( bmhdr->biCompression != BCBI_RGB )
{
UE_LOG(LogImageWrapper, Error, TEXT("RLE compression of BMP images not supported"));
return false;
}
if( bmhdr->biPlanes==1 && ( bmhdr->biBitCount==8 || bmhdr->biBitCount==24 || bmhdr->biBitCount==32 ) )
{
// Set texture properties.
Width = bmhdr->biWidth;
Height = FMath::Abs(bmhdr->biHeight);
Format = ERGBFormat::BGRA;
BitDepth = bmhdr->biBitCount;
return true;
}
else if (bmhdr->biPlanes == 1 && bmhdr->biBitCount == 16)
{
UE_LOG(LogImageWrapper, Error, TEXT("BMP 16 bit format no longer supported. Use terrain tools for importing/exporting heightmaps."));
}
else
{
UE_LOG(LogImageWrapper, Error, TEXT("BMP uses an unsupported format (%i/%i)"), bmhdr->biPlanes, bmhdr->biBitCount);
}
}
return false;
}
bool FBmpImageWrapper::LoadBMPInfoHeader()
{
const FBitmapInfoHeader* bmhdr = (FBitmapInfoHeader *)CompressedData.GetData();
if( bmhdr->biCompression != BCBI_RGB )
{
UE_LOG(LogImageWrapper, Error, TEXT("RLE compression of BMP images not supported"));
return false;
}
if( bmhdr->biPlanes==1 && ( bmhdr->biBitCount==8 || bmhdr->biBitCount==24 || bmhdr->biBitCount==32 ) )
{
// Set texture properties.
Width = bmhdr->biWidth;
Height = FMath::Abs(bmhdr->biHeight);
Format = ERGBFormat::BGRA;
BitDepth = bmhdr->biBitCount;
return true;
}
else if (bmhdr->biPlanes == 1 && bmhdr->biBitCount == 16)
{
UE_LOG(LogImageWrapper, Error, TEXT("BMP 16 bit format no longer supported. Use terrain tools for importing/exporting heightmaps."));
}
else
{
UE_LOG(LogImageWrapper, Error, TEXT("BMP uses an unsupported format (%i/%i)"), bmhdr->biPlanes, bmhdr->biBitCount);
}
return false;
}
@@ -0,0 +1,52 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#pragma once
/**
* BMP implementation of the helper class
*/
class FBmpImageWrapper
: public FImageWrapperBase
{
public:
/**
* Default Constructor.
*/
FBmpImageWrapper(bool bInHasHeader = true, bool bInHalfHeight = false);
public:
/** Helper function used to uncompress BMP data from a buffer */
void UncompressBMPData( const ERGBFormat::Type InFormat, const int32 InBitDepth );
/**
* Load the header information, returns true if successful.
*
* @return true if successful
*/
bool LoadBMPHeader();
/**
* Load the sub-header information, returns true if successful.
*
* @return true if successful
*/
bool LoadBMPInfoHeader();
public:
// FImageWrapper Interface
virtual void Compress( int32 Quality ) override;
virtual void Uncompress( const ERGBFormat::Type InFormat, int32 InBitDepth ) override;
virtual bool SetCompressed( const void* InCompressedData, int32 InCompressedSize ) override;
private:
/** Whether this file has a BMP file header */
bool bHasHeader;
/** BMP as a sub-format of ICO stores its height as half their actual size */
bool bHalfHeight;
};
@@ -0,0 +1,439 @@
// Copyright 1998-2016 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
@@ -0,0 +1,54 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#pragma once
#if WITH_UNREALEXR
PRAGMA_DISABLE_SHADOW_VARIABLE_WARNINGS
#include "ThirdParty/openexr/Deploy/include/ImfIO.h"
#include "ThirdParty/openexr/Deploy/include/ImathBox.h"
#include "ThirdParty/openexr/Deploy/include/ImfChannelList.h"
#include "ThirdParty/openexr/Deploy/include/ImfInputFile.h"
#include "ThirdParty/openexr/Deploy/include/ImfOutputFile.h"
#include "ThirdParty/openexr/Deploy/include/ImfArray.h"
#include "ThirdParty/openexr/Deploy/include/ImfHeader.h"
#include "ThirdParty/openexr/Deploy/include/ImfStdIO.h"
#include "ThirdParty/openexr/Deploy/include/ImfChannelList.h"
#include "ThirdParty/openexr/Deploy/include/ImfRgbaFile.h"
PRAGMA_ENABLE_SHADOW_VARIABLE_WARNINGS
/**
* OpenEXR implementation of the helper class
*/
class FExrImageWrapper
: public FImageWrapperBase
{
public:
/**
* Default Constructor.
*/
FExrImageWrapper();
public:
//~ Begin FImageWrapper Interface
virtual void Compress( int32 Quality ) override;
virtual void Uncompress( const ERGBFormat::Type InFormat, int32 InBitDepth ) override;
virtual bool SetCompressed( const void* InCompressedData, int32 InCompressedSize ) override;
//~ End FImageWrapper Interface
private:
template <Imf::PixelType OutputFormat, typename sourcetype>
void WriteFrameBufferChannel(Imf::FrameBuffer& ImfFrameBuffer, const char* ChannelName, const sourcetype* SrcData, TArray<uint8>& ChannelBuffer);
template <Imf::PixelType OutputFormat, typename sourcetype>
void CompressRaw(const sourcetype* SrcData, bool bIgnoreAlpha);
const char* GetRawChannelName(int ChannelIndex) const;
};
#endif // WITH_UNREALEXR
@@ -0,0 +1,76 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ImageWrapperPrivatePCH.h"
#include "BmpImageSupport.h"
/**
* ICNS image wrapper class.
*/
FIcnsImageWrapper::FIcnsImageWrapper()
: FImageWrapperBase()
{
}
bool FIcnsImageWrapper::SetCompressed( const void* InCompressedData, int32 InCompressedSize )
{
#if PLATFORM_MAC
return FImageWrapperBase::SetCompressed(InCompressedData, InCompressedSize);
#else
return false;
#endif
}
bool FIcnsImageWrapper::SetRaw( const void* InRawData, int32 InRawSize, const int32 InWidth, const int32 InHeight, const ERGBFormat::Type InFormat, const int32 InBitDepth )
{
#if PLATFORM_MAC
return FImageWrapperBase::SetRaw(InRawData, InRawSize, InWidth, InHeight, InFormat, InBitDepth);
#else
return false;
#endif
}
void FIcnsImageWrapper::Compress(int32 Quality)
{
checkf(false, TEXT("ICNS compression not supported"));
}
void FIcnsImageWrapper::Uncompress(const ERGBFormat::Type InFormat, const int32 InBitDepth)
{
#if PLATFORM_MAC
SCOPED_AUTORELEASE_POOL;
NSData* ImageData = [NSData dataWithBytesNoCopy:CompressedData.GetData() length:CompressedData.Num() freeWhenDone:NO];
NSImage* Image = [[NSImage alloc] initWithData:ImageData];
if (Image)
{
NSBitmapImageRep* Bitmap = [NSBitmapImageRep imageRepWithData:[Image TIFFRepresentation]];
if (Bitmap)
{
check(InFormat == ERGBFormat::BGRA || InFormat == ERGBFormat::RGBA);
check(InBitDepth == 8);
RawData.Empty();
RawData.Append([Bitmap bitmapData], [Bitmap bytesPerPlane]);
RawFormat = Format = InFormat;
RawBitDepth = BitDepth = InBitDepth;
Width = [Bitmap pixelsWide];
Height = [Bitmap pixelsHigh];
if (Format == ERGBFormat::BGRA)
{
for (int32 Index = 0; Index < [Bitmap bytesPerPlane]; Index += 4)
{
uint8 Byte = RawData[Index];
RawData[Index] = RawData[Index + 2];
RawData[Index + 2] = Byte;
}
}
}
[Image release];
}
#else
checkf(false, TEXT("ICNS uncompressing not supported on this platform"));
#endif
}
@@ -0,0 +1,27 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#pragma once
/**
* ICNS implementation of the helper class
*/
class FIcnsImageWrapper
: public FImageWrapperBase
{
public:
/**
* Default Constructor.
*/
FIcnsImageWrapper();
public:
// FImageWrapper Interface
virtual bool SetCompressed( const void* InCompressedData, int32 InCompressedSize ) override;
virtual bool SetRaw( const void* InRawData, int32 InRawSize, const int32 InWidth, const int32 InHeight, const ERGBFormat::Type InFormat, const int32 InBitDepth ) override;
virtual void Compress( int32 Quality ) override;
virtual void Uncompress( const ERGBFormat::Type InFormat, int32 InBitDepth ) override;
};
@@ -0,0 +1,163 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ImageWrapperPrivatePCH.h"
#include "BmpImageSupport.h"
#pragma pack(push,1)
struct FIconDirEntry
{
uint8 bWidth; // Width, in pixels, of the image
uint8 bHeight; // Height, in pixels, of the image
uint8 bColorCount; // Number of colors in image (0 if >=8bpp)
uint8 bReserved; // Reserved ( must be 0)
uint16 wPlanes; // Color Planes
uint16 wBitCount; // Bits per pixel
uint32 dwBytesInRes; // How many bytes in this resource?
uint32 dwImageOffset; // Where in the file is this image?
};
#pragma pack(pop)
#pragma pack(push,1)
struct FIconDir
{
uint16 idReserved; // Reserved (must be 0)
uint16 idType; // Resource Type (1 for icons)
uint16 idCount; // How many images?
FIconDirEntry idEntries[1]; // An entry for each image (idCount of 'em)
};
#pragma pack(pop)
#pragma pack(push,1)
struct FRGBQuad
{
uint8 rgbBlue; // Blue channel
uint8 rgbGreen; // Green channel
uint8 rgbRed; // Red channel
uint8 rgbReserved; // Reserved (alpha)
};
#pragma pack(pop)
#pragma pack(push,1)
struct FIconImage
{
FBitmapInfoHeader icHeader; // DIB header
FRGBQuad icColors[1]; // Color table
uint8 icXOR[1]; // DIB bits for XOR mask
uint8 icAND[1]; // DIB bits for AND mask
};
#pragma pack(pop)
/**
* ICO image wrapper class.
*/
FIcoImageWrapper::FIcoImageWrapper()
: FImageWrapperBase()
{
}
void FIcoImageWrapper::Compress( int32 Quality )
{
checkf(false, TEXT("ICO compression not supported"));
}
void FIcoImageWrapper::Uncompress( const ERGBFormat::Type InFormat, const int32 InBitDepth )
{
const uint8* Buffer = CompressedData.GetData();
if(ImageOffset != 0 && ImageSize != 0)
{
SubImageWrapper->Uncompress(InFormat, InBitDepth);
}
}
bool FIcoImageWrapper::SetCompressed( const void* InCompressedData, int32 InCompressedSize )
{
bool bResult = FImageWrapperBase::SetCompressed( InCompressedData, InCompressedSize );
return bResult && LoadICOHeader(); // Fetch the variables from the header info
}
bool FIcoImageWrapper::GetRaw( const ERGBFormat::Type InFormat, int32 InBitDepth, const TArray<uint8>*& OutRawData )
{
LastError.Empty();
Uncompress(InFormat, InBitDepth);
if (LastError.IsEmpty())
{
OutRawData = &SubImageWrapper->GetRawData();
}
return LastError.IsEmpty();
}
bool FIcoImageWrapper::LoadICOHeader()
{
const uint8* Buffer = CompressedData.GetData();
#if WITH_UNREALPNG
TSharedPtr<FPngImageWrapper> PngWrapper = MakeShareable(new FPngImageWrapper);
#endif
TSharedPtr<FBmpImageWrapper> BmpWrapper = MakeShareable(new FBmpImageWrapper(false, true));
bool bFoundImage = false;
const FIconDir* IconHeader = (FIconDir*)(Buffer);
if(IconHeader->idReserved == 0 && IconHeader->idType == 1)
{
// use the largest-width 32-bit dir entry we find
uint32 LargestWidth = 0;
const FIconDirEntry* IconDirEntry = IconHeader->idEntries;
for(int32 Entry = 0; Entry < (int32)IconHeader->idCount; Entry++, IconDirEntry++)
{
const uint32 RealWidth = IconDirEntry->bWidth == 0 ? 256 : IconDirEntry->bWidth;
if( IconDirEntry->wBitCount == 32 && RealWidth > LargestWidth )
{
#if WITH_UNREALPNG
if(PngWrapper->SetCompressed(Buffer + IconDirEntry->dwImageOffset, (int32)IconDirEntry->dwBytesInRes))
{
Width = PngWrapper->GetWidth();
Height = PngWrapper->GetHeight();
Format = PngWrapper->GetFormat();
LargestWidth = RealWidth;
bFoundImage = true;
bIsPng = true;
ImageOffset = IconDirEntry->dwImageOffset;
ImageSize = IconDirEntry->dwBytesInRes;
}
else
#endif
if(BmpWrapper->SetCompressed(Buffer + IconDirEntry->dwImageOffset, (int32)IconDirEntry->dwBytesInRes))
{
// otherwise this should be a BMP icon
Width = BmpWrapper->GetWidth();
Height = BmpWrapper->GetHeight() / 2; // ICO file spec says to divide by 2 here as height refers to combined image & mask height
Format = BmpWrapper->GetFormat();
LargestWidth = RealWidth;
bFoundImage = true;
bIsPng = false;
ImageOffset = IconDirEntry->dwImageOffset;
ImageSize = IconDirEntry->dwBytesInRes;
}
}
}
}
if(bFoundImage)
{
if(bIsPng)
{
SubImageWrapper = PngWrapper;
}
else
{
SubImageWrapper = BmpWrapper;
}
}
return bFoundImage;
}
@@ -0,0 +1,49 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#pragma once
/**
* ICO implementation of the helper class
*/
class FIcoImageWrapper
: public FImageWrapperBase
{
public:
/**
* Default Constructor.
*/
FIcoImageWrapper();
public:
// FImageWrapper Interface
virtual void Compress( int32 Quality ) override;
virtual void Uncompress( const ERGBFormat::Type InFormat, int32 InBitDepth ) override;
virtual bool SetCompressed( const void* InCompressedData, int32 InCompressedSize ) override;
virtual bool GetRaw( const ERGBFormat::Type InFormat, int32 InBitDepth, const TArray<uint8>*& OutRawData ) override;
private:
/**
* Load the header information.
*
* @return true if successful
*/
bool LoadICOHeader();
private:
/** Sub-wrapper component, as icons that contain PNG or BMP data */
TSharedPtr<FImageWrapperBase> SubImageWrapper;
/** Offset into file that we use as image data */
uint32 ImageOffset;
/** Size of image data in file */
uint32 ImageSize;
/** Whether we should use PNG or BMP data */
bool bIsPng;
};
@@ -0,0 +1,106 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ImageWrapperPrivatePCH.h"
/* FImageWrapperBase structors
*****************************************************************************/
FImageWrapperBase::FImageWrapperBase( )
: RawFormat(ERGBFormat::Invalid)
, RawBitDepth(0)
, Format(ERGBFormat::Invalid)
, BitDepth(0)
, Width(0)
, Height(0)
{ }
/* FImageWrapperBase interface
*****************************************************************************/
void FImageWrapperBase::Reset( )
{
LastError.Empty();
RawFormat = ERGBFormat::Invalid;
RawBitDepth = 0;
Format = ERGBFormat::Invalid;
BitDepth = 0;
Width = 0;
Height = 0;
}
void FImageWrapperBase::SetError( const TCHAR* ErrorMessage )
{
LastError = ErrorMessage;
}
/* IImageWrapper structors
*****************************************************************************/
const TArray<uint8>& FImageWrapperBase::GetCompressed(int32 Quality)
{
LastError.Empty();
Compress(Quality);
return CompressedData;
}
bool FImageWrapperBase::GetRaw( const ERGBFormat::Type InFormat, int32 InBitDepth, const TArray<uint8>*& OutRawData )
{
LastError.Empty();
Uncompress(InFormat, InBitDepth);
if (LastError.IsEmpty())
{
OutRawData = &RawData;
}
return LastError.IsEmpty();
}
bool FImageWrapperBase::SetCompressed( const void* InCompressedData, int32 InCompressedSize )
{
if( InCompressedSize > 0 && InCompressedData != nullptr )
{
Reset();
RawData.Empty(); // Invalidates the raw data too
CompressedData.Empty(InCompressedSize);
CompressedData.AddUninitialized(InCompressedSize);
FMemory::Memcpy(CompressedData.GetData(), InCompressedData, InCompressedSize);
return true;
}
return false;
}
bool FImageWrapperBase::SetRaw( const void* InRawData, int32 InRawSize, const int32 InWidth, const int32 InHeight, const ERGBFormat::Type InFormat, const int32 InBitDepth )
{
check(InRawData != NULL);
check(InRawSize > 0);
check(InWidth > 0);
check(InHeight > 0);
Reset();
CompressedData.Empty(); // Invalidates the compressed data too
RawData.Empty(InRawSize);
RawData.AddUninitialized(InRawSize);
FMemory::Memcpy(RawData.GetData(), InRawData, InRawSize);
RawFormat = InFormat;
RawBitDepth = InBitDepth;
Width = InWidth;
Height = InHeight;
return true;
}
@@ -0,0 +1,112 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#pragma once
/**
* The abstract helper class for handling the different image formats
*/
class FImageWrapperBase
: public IImageWrapper
{
public:
/**
* Default Constructor.
*/
FImageWrapperBase( );
public:
/**
* Gets the image's raw data.
*
* @return A read-only byte array containing the data.
*/
const TArray<uint8>& GetRawData() const
{
return RawData;
}
public:
/**
* Compresses the data.
*
* @param Quality The compression quality.
*/
virtual void Compress( int32 Quality ) = 0;
/**
* Resets the local variables.
*/
virtual void Reset( );
/**
* Sets last error message.
*
* @param ErrorMessage The error message to set.
*/
virtual void SetError( const TCHAR* ErrorMessage );
/**
* Function to uncompress our data
*
* @param InFormat How we want to manipulate the RGB data
*/
virtual void Uncompress( const ERGBFormat::Type InFormat, int32 InBitDepth ) = 0;
public:
// IImageWrapper interface
virtual const TArray<uint8>& GetCompressed( int32 Quality = 0 ) override;
virtual int32 GetBitDepth( ) const override
{
return BitDepth;
}
virtual ERGBFormat::Type GetFormat() const override
{
return Format;
}
virtual int32 GetHeight( ) const override
{
return Height;
}
virtual bool GetRaw( const ERGBFormat::Type InFormat, int32 InBitDepth, const TArray<uint8>*& OutRawData ) override;
virtual int32 GetWidth( ) const override
{
return Width;
}
virtual bool SetCompressed( const void* InCompressedData, int32 InCompressedSize ) override;
virtual bool SetRaw( const void* InRawData, int32 InRawSize, const int32 InWidth, const int32 InHeight, const ERGBFormat::Type InFormat, const int32 InBitDepth ) override;
protected:
/** Arrays of compressed/raw data */
TArray<uint8> RawData;
TArray<uint8> CompressedData;
/** Format of the raw data */
TEnumAsByte<ERGBFormat::Type> RawFormat;
int8 RawBitDepth;
/** Format of the image */
TEnumAsByte<ERGBFormat::Type> Format;
/** Bit depth of the image */
int8 BitDepth;
/** Width/Height of the image data */
int32 Width;
int32 Height;
/** Last Error Message. */
FString LastError;
};
@@ -0,0 +1,129 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ImageWrapperPrivatePCH.h"
DEFINE_LOG_CATEGORY(LogImageWrapper);
namespace {
static const uint8 IMAGE_MAGIC_PNG[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
static const uint8 IMAGE_MAGIC_JPEG[] = {0xFF, 0xD8, 0xFF};
static const uint8 IMAGE_MAGIC_BMP[] = {0x42, 0x4D};
static const uint8 IMAGE_MAGIC_ICO[] = {0x00, 0x00, 0x01, 0x00};
static const uint8 IMAGE_MAGIC_EXR[] = {0x76, 0x2F, 0x31, 0x01};
static const uint8 IMAGE_MAGIC_ICNS[] = {0x69, 0x63, 0x6E, 0x73};
/** Internal helper function to verify image signature. */
template <int32 MagicCount> bool StartsWith(const uint8* Content, int32 ContentSize, const uint8 (&Magic)[MagicCount])
{
if (ContentSize < MagicCount)
{
return false;
}
for (int32 I = 0; I < MagicCount; ++I)
{
if ( Content[I] != Magic[I] )
{
return false;
}
}
return true;
}
}
/**
* Image Wrapper module
*/
class FImageWrapperModule
: public IImageWrapperModule
{
public:
// IImageWrapperModule interface
virtual IImageWrapperPtr CreateImageWrapper( const EImageFormat::Type InFormat ) override
{
FImageWrapperBase* ImageWrapper = NULL;
// Allocate a helper for the format type
switch(InFormat)
{
#if WITH_UNREALPNG
case EImageFormat::PNG:
ImageWrapper = new FPngImageWrapper();
break;
#endif // WITH_UNREALPNG
#if WITH_UNREALJPEG
case EImageFormat::JPEG:
ImageWrapper = new FJpegImageWrapper();
break;
case EImageFormat::GrayscaleJPEG:
ImageWrapper = new FJpegImageWrapper(1);
break;
#endif //WITH_UNREALJPEG
case EImageFormat::BMP:
ImageWrapper = new FBmpImageWrapper();
break;
case EImageFormat::ICO:
ImageWrapper = new FIcoImageWrapper();
break;
#if WITH_UNREALEXR
case EImageFormat::EXR:
ImageWrapper = new FExrImageWrapper();
break;
#endif
case EImageFormat::ICNS:
ImageWrapper = new FIcnsImageWrapper();
break;
default:
break;
}
return MakeShareable(ImageWrapper);
}
virtual EImageFormat::Type DetectImageFormat( const void* CompressedData, int32 CompressedSize ) override
{
EImageFormat::Type Format = EImageFormat::Invalid;
if (StartsWith((uint8*) CompressedData, CompressedSize, IMAGE_MAGIC_PNG))
{
Format = EImageFormat::PNG;
}
else if (StartsWith((uint8*) CompressedData, CompressedSize, IMAGE_MAGIC_JPEG))
{
Format = EImageFormat::JPEG; // @Todo: Should we detect grayscale vs non-grayscale?
}
else if (StartsWith((uint8*) CompressedData, CompressedSize, IMAGE_MAGIC_BMP))
{
Format = EImageFormat::BMP;
}
else if (StartsWith((uint8*) CompressedData, CompressedSize, IMAGE_MAGIC_ICO))
{
Format = EImageFormat::ICO;
}
else if (StartsWith((uint8*) CompressedData, CompressedSize, IMAGE_MAGIC_EXR))
{
Format = EImageFormat::EXR;
}
else if (StartsWith((uint8*) CompressedData, CompressedSize, IMAGE_MAGIC_ICNS))
{
Format = EImageFormat::ICNS;
}
return Format;
}
public:
// IModuleInterface interface
virtual void StartupModule( ) override { }
virtual void ShutdownModule( ) override { }
};
IMPLEMENT_MODULE(FImageWrapperModule, ImageWrapper);
@@ -0,0 +1,37 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ImageWrapper.h"
/* Dependencies
*****************************************************************************/
#include "Core.h"
#include "ModuleManager.h"
/* Private includes
*****************************************************************************/
DECLARE_LOG_CATEGORY_EXTERN(LogImageWrapper, Log, All);
#include "ImageWrapperBase.h"
#if WITH_UNREALJPEG
#include "JpegImageWrapper.h"
#endif
#if WITH_UNREALPNG
#include "PngImageWrapper.h"
#endif
#if WITH_UNREALEXR
#include "ExrImageWrapper.h"
#endif
#include "BmpImageWrapper.h"
#include "IcoImageWrapper.h"
#include "IcnsImageWrapper.h"
@@ -0,0 +1,171 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ImageWrapperPrivatePCH.h"
#if WITH_UNREALJPEG
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshadow"
#if PLATFORM_LINUX || PLATFORM_MAC
#pragma clang diagnostic ignored "-Wshift-negative-value" // clang 3.7.0
#endif
#endif
#include "jpgd.h"
#include "jpgd.cpp"
#include "jpge.h"
#include "jpge.cpp"
#ifdef __clang__
#pragma clang diagnostic pop
#endif
DEFINE_LOG_CATEGORY_STATIC(JPEGLog, Log, All);
// Only allow one thread to use JPEG decoder at a time (it's not thread safe)
FCriticalSection GJPEGSection;
/* FJpegImageWrapper structors
*****************************************************************************/
FJpegImageWrapper::FJpegImageWrapper( int32 InNumComponents )
: FImageWrapperBase(),
NumComponents(InNumComponents)
{ }
/* FImageWrapperBase interface
*****************************************************************************/
bool FJpegImageWrapper::SetCompressed( const void* InCompressedData, int32 InCompressedSize )
{
jpgd::jpeg_decoder_mem_stream jpeg_memStream( (uint8*)InCompressedData, InCompressedSize );
jpgd::jpeg_decoder decoder(&jpeg_memStream);
if ( decoder.get_error_code() != jpgd::JPGD_SUCCESS )
return false;
bool bResult = FImageWrapperBase::SetCompressed( InCompressedData, InCompressedSize );
// We don't support 16 bit jpegs
BitDepth = 8;
Width = decoder.get_width();
Height = decoder.get_height();
switch ( decoder.get_num_components() )
{
case 1:
Format = ERGBFormat::Gray;
break;
case 3:
Format = ERGBFormat::RGBA;
break;
default:
return false;
}
return bResult;
}
bool FJpegImageWrapper::SetRaw( const void* InRawData, int32 InRawSize, const int32 InWidth, const int32 InHeight, const ERGBFormat::Type InFormat, const int32 InBitDepth )
{
check((InFormat == ERGBFormat::RGBA || InFormat == ERGBFormat::BGRA || InFormat == ERGBFormat::Gray) && InBitDepth == 8);
bool bResult = FImageWrapperBase::SetRaw( InRawData, InRawSize, InWidth, InHeight, InFormat, InBitDepth );
return bResult;
}
void FJpegImageWrapper::Compress( int32 Quality )
{
if (CompressedData.Num() == 0)
{
FScopeLock JPEGLock(&GJPEGSection);
if (Quality == 0) {Quality = 85;}
ensure(Quality >= 1 && Quality <= 100);
Quality = FMath::Clamp(Quality, 1, 100);
check( RawData.Num() );
check( Width > 0 );
check( Height > 0 );
// re-order components if required - JPEGs expect RGBA
if(RawFormat == ERGBFormat::BGRA)
{
FColor* Colors = (FColor*)RawData.GetData();
const int32 NumColors = RawData.Num() / 4;
for(int32 ColorIndex = 0; ColorIndex < NumColors; ColorIndex++)
{
uint8 Temp = Colors[ColorIndex].B;
Colors[ColorIndex].B = Colors[ColorIndex].R;
Colors[ColorIndex].R = Temp;
}
}
CompressedData.Empty();
CompressedData.AddUninitialized(RawData.Num());
int OutBufferSize = CompressedData.Num();
jpge::params Parameters;
Parameters.m_quality = Quality;
bool bSuccess = jpge::compress_image_to_jpeg_file_in_memory(
CompressedData.GetData(), OutBufferSize, Width, Height, NumComponents, RawData.GetData(), Parameters);
check(bSuccess);
CompressedData.RemoveAt(OutBufferSize, CompressedData.Num() - OutBufferSize);
}
}
void FJpegImageWrapper::Uncompress( const ERGBFormat::Type InFormat, int32 InBitDepth )
{
// Ensure we haven't already uncompressed the file.
if ( RawData.Num() != 0 )
{
return;
}
// Get the number of channels we need to extract
int Channels = 0;
if ( ( InFormat == ERGBFormat::RGBA || InFormat == ERGBFormat::BGRA ) && InBitDepth == 8 )
{
Channels = 4;
}
else if ( InFormat == ERGBFormat::Gray && InBitDepth == 8 )
{
Channels = 1;
}
else
{
check( false );
}
FScopeLock JPEGLock( &GJPEGSection );
check( CompressedData.Num() );
int32 NumColors;
uint8* OutData = jpgd::decompress_jpeg_image_from_memory(
CompressedData.GetData(), CompressedData.Num(), &Width, &Height, &NumColors, Channels);
RawData.Empty();
RawData.AddUninitialized( Width * Height * Channels );
if (OutData)
{
FMemory::Memcpy(RawData.GetData(), OutData, RawData.Num());
FMemory::Free(OutData);
}
}
#endif //WITH_JPEG
@@ -0,0 +1,37 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#pragma once
#if WITH_UNREALJPEG
/**
* Uncompresses JPEG data to raw 24bit RGB image that can be used by Unreal textures.
*
* Source code for JPEG decompression from http://code.google.com/p/jpeg-compressor/
*/
class FJpegImageWrapper
: public FImageWrapperBase
{
public:
/**
* Default constructor.
*/
FJpegImageWrapper(int32 InNumComponents=4);
public:
// FImageWrapperBase interface
virtual bool SetCompressed( const void* InCompressedData, int32 InCompressedSize ) override;
virtual bool SetRaw( const void* InRawData, int32 InRawSize, const int32 InWidth, const int32 InHeight, const ERGBFormat::Type InFormat, const int32 InBitDepth ) override;
virtual void Uncompress( const ERGBFormat::Type InFormat, int32 InBitDepth ) override;
virtual void Compress(int32 Quality) override;
private:
int32 NumComponents;
};
#endif //WITH_JPEG
@@ -0,0 +1,491 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "ImageWrapperPrivatePCH.h"
#if WITH_UNREALPNG
// Disable warning "interaction between '_setjmp' and C++ object destruction is non-portable"
#if _MSC_VER
#pragma warning(push)
#pragma warning(disable:4611)
#endif
/** Only allow one thread to use libpng at a time (it's not thread safe) */
FCriticalSection GPNGSection;
/* Local helper classes
*****************************************************************************/
/**
* Error type for PNG reading issue.
*/
struct FPNGImageCRCError
{
FPNGImageCRCError(FString InErrorText)
: ErrorText(MoveTemp(InErrorText))
{ }
FString ErrorText;
};
/**
* Guard that safely releases PNG reader resources.
*/
class PNGReadGuard
{
public:
PNGReadGuard( png_structp* InReadStruct, png_infop* InInfo )
: png_ptr(InReadStruct)
, info_ptr(InInfo)
, PNGRowPointers(NULL)
{
}
~PNGReadGuard()
{
if (PNGRowPointers != NULL)
{
png_free( *png_ptr, *PNGRowPointers );
}
png_destroy_read_struct( png_ptr, info_ptr, NULL );
}
void SetRowPointers( png_bytep** InRowPointers )
{
PNGRowPointers = InRowPointers;
}
private:
png_structp* png_ptr;
png_infop* info_ptr;
png_bytep** PNGRowPointers;
};
/**
* Guard that safely releases PNG Writer resources
*/
class PNGWriteGuard
{
public:
PNGWriteGuard( png_structp* InWriteStruct, png_infop* InInfo )
: PNGWriteStruct(InWriteStruct)
, info_ptr(InInfo)
, PNGRowPointers(NULL)
{
}
~PNGWriteGuard()
{
if (PNGRowPointers != NULL)
{
png_free( *PNGWriteStruct, *PNGRowPointers );
}
png_destroy_write_struct( PNGWriteStruct, info_ptr );
}
void SetRowPointers( png_bytep** InRowPointers )
{
PNGRowPointers = InRowPointers;
}
private:
png_structp* PNGWriteStruct;
png_infop* info_ptr;
png_bytep** PNGRowPointers;
};
/* FPngImageWrapper structors
*****************************************************************************/
FPngImageWrapper::FPngImageWrapper()
: FImageWrapperBase()
, ReadOffset( 0 )
, ColorType(0)
, Channels(0)
{ }
/* FImageWrapper interface
*****************************************************************************/
void FPngImageWrapper::Compress( int32 Quality )
{
if (!CompressedData.Num())
{
// thread safety
FScopeLock PNGLock(&GPNGSection);
check(RawData.Num());
check(Width > 0);
check(Height > 0);
// Reset to the beginning of file so we can use png_read_png(), which expects to start at the beginning.
ReadOffset = 0;
png_structp png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, this, FPngImageWrapper::user_error_fn, FPngImageWrapper::user_warning_fn );
check(png_ptr);
png_infop info_ptr = png_create_info_struct( png_ptr );
check(info_ptr);
PNGWriteGuard PNGGuard(&png_ptr, &info_ptr);
{
png_set_compression_level(png_ptr, Z_BEST_SPEED);
png_set_IHDR(png_ptr, info_ptr, Width, Height, RawBitDepth, (RawFormat == ERGBFormat::Gray) ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_write_fn(png_ptr, this, FPngImageWrapper::user_write_compressed, FPngImageWrapper::user_flush_data);
png_bytep* row_pointers = (png_bytep*) png_malloc( png_ptr, Height*sizeof(png_bytep) );
const uint32 PixelChannels = (RawFormat == ERGBFormat::Gray) ? 1 : 4;
const uint32 BytesPerPixel = (RawBitDepth * PixelChannels) / 8;
const uint32 BytesPerRow = BytesPerPixel * Width;
PNGGuard.SetRowPointers( &row_pointers );
for (int32 i = 0; i < Height; i++)
{
row_pointers[i]= &RawData[i * BytesPerRow];
}
png_set_rows(png_ptr, info_ptr, row_pointers);
uint32 Transform = (RawFormat == ERGBFormat::BGRA) ? PNG_TRANSFORM_BGR : PNG_TRANSFORM_IDENTITY;
// PNG files store 16-bit pixels in network byte order (big-endian, ie. most significant bits first).
#if PLATFORM_LITTLE_ENDIAN
// We're little endian so we need to swap
if (RawBitDepth == 16)
{
Transform |= PNG_TRANSFORM_SWAP_ENDIAN;
}
#endif
if (!setjmp(SetjmpBuffer))
{
png_write_png(png_ptr, info_ptr, Transform, NULL);
}
}
}
}
void FPngImageWrapper::Reset( )
{
FImageWrapperBase::Reset();
ReadOffset = 0;
ColorType = 0;
Channels = 0;
}
bool FPngImageWrapper::SetCompressed( const void* InCompressedData, int32 InCompressedSize )
{
bool bResult = FImageWrapperBase::SetCompressed( InCompressedData, InCompressedSize );
return bResult && LoadPNGHeader(); // Fetch the variables from the header info
}
void FPngImageWrapper::Uncompress( const ERGBFormat::Type InFormat, const int32 InBitDepth )
{
if( !RawData.Num() || InFormat != RawFormat || InBitDepth != RawBitDepth)
{
check( CompressedData.Num() );
UncompressPNGData( InFormat, InBitDepth );
}
}
void FPngImageWrapper::UncompressPNGData( const ERGBFormat::Type InFormat, const int32 InBitDepth )
{
// thread safety
FScopeLock PNGLock(&GPNGSection);
check( CompressedData.Num() );
check( Width > 0 );
check( Height > 0 );
// Note that PNGs on PC tend to be BGR
check( InFormat == ERGBFormat::BGRA || InFormat == ERGBFormat::RGBA || InFormat == ERGBFormat::Gray ) // Other formats unsupported at present
check( InBitDepth == 8 || InBitDepth == 16 ) // Other formats unsupported at present
// Reset to the beginning of file so we can use png_read_png(), which expects to start at the beginning.
ReadOffset = 0;
png_structp png_ptr = png_create_read_struct_2( PNG_LIBPNG_VER_STRING, this, FPngImageWrapper::user_error_fn, FPngImageWrapper::user_warning_fn, NULL, FPngImageWrapper::user_malloc, FPngImageWrapper::user_free);
check( png_ptr );
png_infop info_ptr = png_create_info_struct( png_ptr );
check( info_ptr );
#if !PLATFORM_EXCEPTIONS_DISABLED
try
#endif
{
PNGReadGuard PNGGuard( &png_ptr, &info_ptr );
{
if (ColorType == PNG_COLOR_TYPE_PALETTE)
{
png_set_palette_to_rgb(png_ptr);
}
if ((ColorType & PNG_COLOR_MASK_COLOR) == 0 && BitDepth < 8)
{
png_set_expand_gray_1_2_4_to_8(png_ptr);
}
// Insert alpha channel with full opacity for RGB images without alpha
if ((ColorType & PNG_COLOR_MASK_ALPHA) == 0 && (InFormat == ERGBFormat::BGRA || InFormat == ERGBFormat::RGBA))
{
// png images don't set PNG_COLOR_MASK_ALPHA if they have alpha from a tRNS chunk, but png_set_add_alpha seems to be safe regardless
if ((ColorType & PNG_COLOR_MASK_COLOR) == 0)
{
png_set_tRNS_to_alpha(png_ptr);
}
else if (ColorType == PNG_COLOR_TYPE_PALETTE)
{
png_set_tRNS_to_alpha(png_ptr);
}
if (InBitDepth == 8)
{
png_set_add_alpha(png_ptr, 0xff , PNG_FILLER_AFTER);
}
else if (InBitDepth == 16)
{
png_set_add_alpha(png_ptr, 0xffff , PNG_FILLER_AFTER);
}
}
// Calculate Pixel Depth
const uint32 PixelChannels = (InFormat == ERGBFormat::Gray) ? 1 : 4;
const uint32 BytesPerPixel = (InBitDepth * PixelChannels) / 8;
const uint32 BytesPerRow = BytesPerPixel * Width;
RawData.Empty(Height * BytesPerRow);
RawData.AddUninitialized(Height * BytesPerRow);
png_set_read_fn( png_ptr, this, FPngImageWrapper::user_read_compressed );
png_bytep* row_pointers = (png_bytep*) png_malloc( png_ptr, Height*sizeof(png_bytep) );
PNGGuard.SetRowPointers(&row_pointers);
for (int32 i = 0; i < Height; i++)
{
row_pointers[i]= &RawData[i * BytesPerRow];
}
png_set_rows(png_ptr, info_ptr, row_pointers);
uint32 Transform = (InFormat == ERGBFormat::BGRA) ? PNG_TRANSFORM_BGR : PNG_TRANSFORM_IDENTITY;
// PNG files store 16-bit pixels in network byte order (big-endian, ie. most significant bits first).
#if PLATFORM_LITTLE_ENDIAN
// We're little endian so we need to swap
if (BitDepth == 16)
{
Transform |= PNG_TRANSFORM_SWAP_ENDIAN;
}
#endif
// Convert grayscale png to RGB if requested
if ((ColorType & PNG_COLOR_MASK_COLOR) == 0 &&
(InFormat == ERGBFormat::RGBA || InFormat == ERGBFormat::BGRA))
{
Transform |= PNG_TRANSFORM_GRAY_TO_RGB;
}
// Convert RGB png to grayscale if requested
if ((ColorType & PNG_COLOR_MASK_COLOR) != 0 && InFormat == ERGBFormat::Gray)
{
png_set_rgb_to_gray_fixed(png_ptr, 2 /* warn if image is in color */, -1, -1);
}
// Strip alpha channel if requested output is grayscale
if (InFormat == ERGBFormat::Gray)
{
// this is not necessarily the best option, instead perhaps:
// png_color background = {0,0,0};
// png_set_background(png_ptr, &background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
Transform |= PNG_TRANSFORM_STRIP_ALPHA;
}
// Reduce 16-bit to 8-bit if requested
if (BitDepth == 16 && InBitDepth == 8)
{
#if PNG_LIBPNG_VER >= 10504
check(0); // Needs testing
Transform |= PNG_TRANSFORM_SCALE_16;
#else
Transform |= PNG_TRANSFORM_STRIP_16;
#endif
}
// Increase 8-bit to 16-bit if requested
if (BitDepth <= 8 && InBitDepth == 16)
{
#if PNG_LIBPNG_VER >= 10504
check(0); // Needs testing
Transform |= PNG_TRANSFORM_EXPAND_16;
#else
// Expanding 8-bit images to 16-bit via transform needs a libpng update
check(0);
#endif
}
if (!setjmp(SetjmpBuffer))
{
png_read_png(png_ptr, info_ptr, Transform, NULL);
}
}
}
#if !PLATFORM_EXCEPTIONS_DISABLED
catch (const FPNGImageCRCError& e)
{
/**
* libPNG has a known issue in version 1.5.2 causing
* an unhandled exception upon a CRC error. This code
* catches our custom exception thrown in user_error_fn.
*/
UE_LOG(LogImageWrapper, Error, TEXT("%s"), *e.ErrorText);
}
#endif
RawFormat = InFormat;
RawBitDepth = InBitDepth;
}
/* FPngImageWrapper implementation
*****************************************************************************/
bool FPngImageWrapper::IsPNG() const
{
check( CompressedData.Num() );
const int32 PNGSigSize = sizeof(png_size_t);
if (CompressedData.Num() > PNGSigSize)
{
png_size_t PNGSignature = *reinterpret_cast<const png_size_t*>(CompressedData.GetData());
return (0 == png_sig_cmp(reinterpret_cast<png_bytep>(&PNGSignature), 0, PNGSigSize));
}
return false;
}
bool FPngImageWrapper::LoadPNGHeader()
{
check(CompressedData.Num());
// Test whether the data this PNGLoader is pointing at is a PNG or not.
if (IsPNG())
{
// thread safety
FScopeLock PNGLock(&GPNGSection);
png_structp png_ptr = png_create_read_struct_2( PNG_LIBPNG_VER_STRING, this, FPngImageWrapper::user_error_fn, FPngImageWrapper::user_warning_fn, NULL, FPngImageWrapper::user_malloc, FPngImageWrapper::user_free );
check(png_ptr);
png_infop info_ptr = png_create_info_struct( png_ptr );
check(info_ptr);
PNGReadGuard PNGGuard(&png_ptr, &info_ptr);
{
png_set_read_fn(png_ptr, this, FPngImageWrapper::user_read_compressed);
png_read_info(png_ptr, info_ptr);
Width = info_ptr->width;
Height = info_ptr->height;
ColorType = info_ptr->color_type;
BitDepth = info_ptr->bit_depth;
Channels = info_ptr->channels;
Format = (ColorType & PNG_COLOR_MASK_COLOR) ? ERGBFormat::RGBA : ERGBFormat::Gray;
}
return true;
}
return false;
}
/* FPngImageWrapper static implementation
*****************************************************************************/
void FPngImageWrapper::user_read_compressed( png_structp png_ptr, png_bytep data, png_size_t length )
{
FPngImageWrapper* ctx = (FPngImageWrapper*) png_get_io_ptr(png_ptr);
check(ctx->ReadOffset + length <= (uint32)ctx->CompressedData.Num());
FMemory::Memcpy(data, &ctx->CompressedData[ctx->ReadOffset], length);
ctx->ReadOffset += length;
}
void FPngImageWrapper::user_write_compressed( png_structp png_ptr, png_bytep data, png_size_t length )
{
FPngImageWrapper* ctx = (FPngImageWrapper*) png_get_io_ptr(png_ptr);
int32 Offset = ctx->CompressedData.AddUninitialized( length);
FMemory::Memcpy(&ctx->CompressedData[Offset], data, length);
}
void FPngImageWrapper::user_flush_data( png_structp png_ptr )
{
}
void FPngImageWrapper::user_error_fn( png_structp png_ptr, png_const_charp error_msg )
{
FPngImageWrapper* ctx = (FPngImageWrapper*)png_get_io_ptr( png_ptr );
{
FString ErrorMsg = ANSI_TO_TCHAR(error_msg);
ctx->SetError(*ErrorMsg);
UE_LOG(LogImageWrapper, Error, TEXT("PNG Error: %s"), *ErrorMsg);
#if !PLATFORM_EXCEPTIONS_DISABLED
/**
* libPNG has a known issue in version 1.5.2 causing
* an unhandled exception upon a CRC error. This code
* detects the error manually and throws our own
* exception to be handled.
*/
if (ErrorMsg.Contains(TEXT("CRC error")))
{
throw FPNGImageCRCError(ErrorMsg);
}
#endif
}
// Ensure that FString is destructed prior to executing the longjmp
longjmp(ctx->SetjmpBuffer, 1);
}
void FPngImageWrapper::user_warning_fn( png_structp png_ptr, png_const_charp warning_msg )
{
UE_LOG(LogImageWrapper, Warning, TEXT("PNG Warning: %s"), ANSI_TO_TCHAR(warning_msg));
}
void* FPngImageWrapper::user_malloc( png_structp /*png_ptr*/, png_size_t size)
{
check(size > 0);
return FMemory::Malloc(size);
}
void FPngImageWrapper::user_free(png_structp /*png_ptr*/, png_voidp struct_ptr )
{
check(struct_ptr);
FMemory::Free(struct_ptr);
}
// Renable warning "interaction between '_setjmp' and C++ object destruction is non-portable"
#if _MSC_VER
#pragma warning(pop)
#endif
#endif //WITH_UNREALPNG
@@ -0,0 +1,92 @@
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#pragma once
#if WITH_UNREALPNG
#include "ThirdParty/zlib/zlib-1.2.5/Inc/zlib.h"
#include "ThirdParty/libPNG/libPNG-1.5.2/png.h"
#include "ThirdParty/libPNG/libPNG-1.5.2/pnginfo.h"
#include <setjmp.h>
/**
* PNG implementation of the helper class.
*
* The implementation of this class is almost entirely based on the sample from the libPNG documentation.
* See http://www.libpng.org/pub/png/libpng-1.2.5-manual.html for details.
*
* InitCompressed and InitRaw will set initial state and you will then be able to fill in Raw or
* CompressedData by calling Uncompress or Compress respectively.
*/
class FPngImageWrapper
: public FImageWrapperBase
{
public:
/**
* Default Constructor.
*/
FPngImageWrapper();
public:
//~ Begin FImageWrapper Interface
virtual void Compress( int32 Quality ) override;
virtual void Reset( ) override;
virtual bool SetCompressed( const void* InCompressedData, int32 InCompressedSize ) override;
virtual void Uncompress( const ERGBFormat::Type InFormat, int32 InBitDepth ) override;
//~ End FImageWrapper Interface
public:
/**
* Query whether this is a valid PNG type.
*
* @return true if data a PNG
*/
bool IsPNG() const;
/**
* Load the header information, returns true if successful.
*
* @return true if successful
*/
bool LoadPNGHeader();
/** Helper function used to uncompress PNG data from a buffer */
void UncompressPNGData( const ERGBFormat::Type InFormat, const int32 InBitDepth );
protected:
// Callbacks for the pnglibs
static void user_read_compressed( png_structp png_ptr, png_bytep data, png_size_t length );
static void user_write_compressed( png_structp png_ptr, png_bytep data, png_size_t length );
static void user_flush_data( png_structp png_ptr );
static void user_error_fn( png_structp png_ptr, png_const_charp error_msg );
static void user_warning_fn( png_structp png_ptr, png_const_charp warning_msg );
static void* user_malloc( png_structp png_ptr, png_size_t size);
static void user_free(png_structp png_ptr, png_voidp struct_ptr );
private:
// The read offset into our array.
int32 ReadOffset;
// The color type as defined in the header.
int32 ColorType;
// The number of channels.
uint8 Channels;
// setjmp buffer for error recovery
jmp_buf SetjmpBuffer;
};
#endif //WITH_UNREALPNG