Files
UnrealEngineUWP/Engine/Source/Runtime/ImageWrapper/Private/Formats/JpegImageWrapper.cpp
bryan sefcik 07894f4a07 Removed redundant private include paths from build.cs files.
Fixed include paths to be relative to the private or public folders.
Hid or removed includes that reached into other private module folders.
Updated PublicInclude paths when necessary.

#jira
#preflight 631a717cec45fbf3d74d4ba7

[CL 21916033 by bryan sefcik in ue5-main branch]
2022-09-09 00:53:22 -04:00

393 lines
9.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Formats/JpegImageWrapper.h"
#include "Math/Color.h"
#include "Misc/ScopeLock.h"
#include "ImageWrapperPrivate.h"
#if WITH_UNREALJPEG
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshadow"
#if PLATFORM_UNIX || PLATFORM_MAC
#pragma clang diagnostic ignored "-Wshift-negative-value" // clang 3.7.0
#endif
#endif
#if WITH_LIBJPEGTURBO
THIRD_PARTY_INCLUDES_START
#pragma push_macro("DLLEXPORT")
#undef DLLEXPORT // libjpeg-turbo defines DLLEXPORT as well
#include "turbojpeg.h"
#pragma pop_macro("DLLEXPORT")
THIRD_PARTY_INCLUDES_END
#else
PRAGMA_DISABLE_SHADOW_VARIABLE_WARNINGS
#include "jpgd.h"
#include "jpgd.cpp"
#include "jpge.h"
#include "jpge.cpp"
PRAGMA_ENABLE_SHADOW_VARIABLE_WARNINGS
#endif // WITH_LIBJPEGTURBO
#ifdef __clang__
#pragma clang diagnostic pop
#endif
DEFINE_LOG_CATEGORY_STATIC(JPEGLog, Log, All);
#if WITH_LIBJPEGTURBO
namespace
{
int ConvertTJpegPixelFormat(ERGBFormat InFormat)
{
switch (InFormat)
{
case ERGBFormat::BGRA: return TJPF_BGRA;
case ERGBFormat::Gray: return TJPF_GRAY;
case ERGBFormat::RGBA: return TJPF_RGBA;
default: check(0); return TJPF_RGBA;
}
}
}
#endif // WITH_LIBJPEGTURBO
// 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)
#if WITH_LIBJPEGTURBO
, Compressor(tjInitCompress())
, Decompressor(tjInitDecompress())
#endif // WITH_LIBJPEGTURBO
{ }
#if WITH_LIBJPEGTURBO
FJpegImageWrapper::~FJpegImageWrapper()
{
FScopeLock JPEGLock(&GJPEGSection);
if (Compressor)
{
tjDestroy(Compressor);
}
if (Decompressor)
{
tjDestroy(Decompressor);
}
}
#endif // WITH_LIBJPEGTURBO
/* FImageWrapperBase interface
*****************************************************************************/
bool FJpegImageWrapper::SetCompressed(const void* InCompressedData, int64 InCompressedSize)
{
#if WITH_LIBJPEGTURBO
return SetCompressedTurbo(InCompressedData, InCompressedSize);
#else
// jpgd doesn't support 64-bit sizes.
if (InCompressedSize < 0 || InCompressedSize > MAX_uint32)
{
return false;
}
jpgd::jpeg_decoder_mem_stream jpeg_memStream((uint8*)InCompressedData, (uint32)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;
#endif
}
// CanSetRawFormat returns true if SetRaw will accept this format
bool FJpegImageWrapper::CanSetRawFormat(const ERGBFormat InFormat, const int32 InBitDepth) const
{
return ((InFormat == ERGBFormat::RGBA || InFormat == ERGBFormat::BGRA || InFormat == ERGBFormat::Gray) && InBitDepth == 8);
}
// returns InFormat if supported, else maps to something supported
ERawImageFormat::Type FJpegImageWrapper::GetSupportedRawFormat(const ERawImageFormat::Type InFormat) const
{
switch(InFormat)
{
case ERawImageFormat::G8:
case ERawImageFormat::BGRA8:
return InFormat; // directly supported
case ERawImageFormat::BGRE8:
case ERawImageFormat::RGBA16:
case ERawImageFormat::RGBA16F:
case ERawImageFormat::RGBA32F:
case ERawImageFormat::G16:
case ERawImageFormat::R16F:
case ERawImageFormat::R32F:
return ERawImageFormat::BGRA8; // needs conversion
default:
check(0);
return ERawImageFormat::BGRA8;
};
}
bool FJpegImageWrapper::SetRaw(const void* InRawData, int64 InRawSize, const int32 InWidth, const int32 InHeight, const ERGBFormat InFormat, const int32 InBitDepth, const int32 InBytesPerRow)
{
check((InFormat == ERGBFormat::RGBA || InFormat == ERGBFormat::BGRA || InFormat == ERGBFormat::Gray) && InBitDepth == 8);
bool bResult = FImageWrapperBase::SetRaw(InRawData, InRawSize, InWidth, InHeight, InFormat, InBitDepth, InBytesPerRow);
return bResult;
}
void FJpegImageWrapper::Compress(int32 Quality)
{
if (Quality == 0)
{
//default
Quality = 85;
}
else if (Quality == (int32)EImageCompressionQuality::Uncompressed)
{
// fix = 1 (Uncompressed) was previously treated as max-compress
Quality = 100;
}
else
{
ensure(Quality >= 1 && Quality <= 100);
#define JPEG_QUALITY_MIN 40
// JPEG should not be used below quality JPEG_QUALITY_MIN
Quality = FMath::Clamp(Quality, JPEG_QUALITY_MIN, 100);
}
#if WITH_LIBJPEGTURBO
CompressTurbo(Quality);
#else
if (CompressedData.Num() == 0)
{
FScopeLock JPEGLock(&GJPEGSection);
check(RawData.Num());
check(Width > 0);
check(Height > 0);
// re-order components if required - JPEGs expect RGBA
if(Format == ERGBFormat::BGRA)
{
FColor* Colors = (FColor*)RawData.GetData();
const int32 NumColors = RawData.Num() / 4;
for(int32 ColorIndex = 0; ColorIndex < NumColors; ColorIndex++)
{
Colors[ColorIndex].B = Colors[ColorIndex].B ^ Colors[ColorIndex].R;
Colors[ColorIndex].R = Colors[ColorIndex].R ^ Colors[ColorIndex].B;
Colors[ColorIndex].B = Colors[ColorIndex].B ^ Colors[ColorIndex].R;
}
}
CompressedData.Reset(RawData.Num());
CompressedData.AddUninitialized(RawData.Num());
// Note: OutBufferSize intentionally uses int64_t type as that's what jpge::compress_image_to_jpeg_file_in_memory expects.
// UE int64 type is not compatible with int64_t on all compilers (int64_t may be `long`, while int64 is `long long`).
int64_t 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((int64)OutBufferSize, CompressedData.Num() - (int64)OutBufferSize);
}
#endif
}
void FJpegImageWrapper::Uncompress(const ERGBFormat InFormat, int32 InBitDepth)
{
#if WITH_LIBJPEGTURBO
UncompressTurbo(InFormat, InBitDepth);
#else
// 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);
return;
}
FScopeLock JPEGLock(&GJPEGSection);
check(CompressedData.Num());
int32 NumColors;
uint8* OutData = jpgd::decompress_jpeg_image_from_memory(
CompressedData.GetData(), CompressedData.Num(), &Width, &Height, &NumColors, Channels, (int)InFormat);
if (OutData)
{
RawData.Reset(Width * Height * Channels);
RawData.AddUninitialized(Width * Height * Channels);
FMemory::Memcpy(RawData.GetData(), OutData, RawData.Num());
FMemory::Free(OutData);
}
else
{
UE_LOG(LogImageWrapper, Error, TEXT("JPEG Decompress Error"));
RawData.Empty();
}
#endif
}
#if WITH_LIBJPEGTURBO
bool FJpegImageWrapper::SetCompressedTurbo(const void* InCompressedData, int64 InCompressedSize)
{
FScopeLock JPEGLock(&GJPEGSection);
check(Decompressor);
int ImageWidth;
int ImageHeight;
int SubSampling;
int ColorSpace;
if (tjDecompressHeader3(Decompressor, reinterpret_cast<const uint8*>(InCompressedData), InCompressedSize, &ImageWidth, &ImageHeight, &SubSampling, &ColorSpace) != 0)
{
return false;
}
const bool bResult = FImageWrapperBase::SetCompressed(InCompressedData, InCompressedSize);
// set after call to base SetCompressed as it will reset members
Width = ImageWidth;
Height = ImageHeight;
BitDepth = 8; // We don't support 16 bit jpegs
Format = SubSampling == TJSAMP_GRAY ? ERGBFormat::Gray : ERGBFormat::RGBA;
return bResult;
}
void FJpegImageWrapper::CompressTurbo(int32 Quality)
{
if (CompressedData.Num() == 0)
{
FScopeLock JPEGLock(&GJPEGSection);
// Quality mapping should have already been done
check( Quality >= JPEG_QUALITY_MIN && Quality <= 100 );
check(Compressor);
check(RawData.Num());
check(Width > 0);
check(Height > 0);
check(BitDepth == 8);
const int PixelFormat = ConvertTJpegPixelFormat(Format);
const int Subsampling = TJSAMP_420;
const int Flags = TJFLAG_NOREALLOC | TJFLAG_FASTDCT;
unsigned long OutBufferSize = tjBufSize(Width, Height, Subsampling);
CompressedData.SetNum(OutBufferSize);
unsigned char* OutBuffer = CompressedData.GetData();
int BytesPerRow = GetBytesPerRow();
const bool bSuccess = tjCompress2(Compressor, RawData.GetData(), Width, BytesPerRow, Height, PixelFormat, &OutBuffer, &OutBufferSize, Subsampling, Quality, Flags) == 0;
check(bSuccess);
CompressedData.SetNum(OutBufferSize);
}
}
void FJpegImageWrapper::UncompressTurbo(const ERGBFormat 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(Decompressor);
check(CompressedData.Num());
RawData.Reset(Width * Height * Channels);
RawData.AddUninitialized(Width * Height * Channels);
const int PixelFormat = ConvertTJpegPixelFormat(InFormat);
const int Flags = TJFLAG_NOREALLOC | TJFLAG_FASTDCT;
if (tjDecompress2(Decompressor, CompressedData.GetData(), CompressedData.Num(), RawData.GetData(), Width, 0, Height, PixelFormat, Flags) != 0)
{
UE_LOG(LogImageWrapper, Error, TEXT("JPEG Decompress Error"));
RawData.Empty();
return;
}
}
#endif // WITH_LIBJPEGTURBO
#endif //WITH_JPEG