Files
UnrealEngine/Engine/Source/Runtime/ImageWrapper/Private/Formats/PngImageWrapper.cpp
2025-05-18 13:04:45 +08:00

791 lines
23 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Formats/PngImageWrapper.h"
#include "ImageWrapperPrivate.h"
#include "Containers/Utf8String.h"
#include "Math/GuardedInt.h"
#include "Misc/ScopeLock.h"
#include "ImageCoreUtils.h"
#if WITH_UNREALPNG
// Disable warning "interaction between '_setjmp' and C++ object destruction is non-portable"
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4611)
#endif
#if PLATFORM_ANDROID
/** Only allow one thread to use libpng at a time (it's not thread safe) */
static FCriticalSection GPNGSection;
#endif
/* 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;
}
TArray<png_text> MetadataKeyValuePairs;
TArray<FUtf8String> MetadataStrings;
private:
png_structp* PNGWriteStruct;
png_infop* info_ptr;
png_bytep* PNGRowPointers;
};
/* FPngImageWrapper structors
*****************************************************************************/
FPngImageWrapper::FPngImageWrapper()
: FImageWrapperBase()
, ReadOffset(0)
, ColorType(0)
, Channels(0)
{ }
/* FImageWrapper interface
*****************************************************************************/
// CanSetRawFormat returns true if SetRaw will accept this format
bool FPngImageWrapper::CanSetRawFormat(const ERGBFormat InFormat, const int32 InBitDepth) const
{
return (InFormat == ERGBFormat::RGBA || InFormat == ERGBFormat::BGRA || InFormat == ERGBFormat::Gray) &&
( InBitDepth == 8 || InBitDepth == 16 );
}
// returns InFormat if supported, else maps to something supported
ERawImageFormat::Type FPngImageWrapper::GetSupportedRawFormat(const ERawImageFormat::Type InFormat) const
{
switch(InFormat)
{
case ERawImageFormat::G8:
case ERawImageFormat::BGRA8:
case ERawImageFormat::RGBA16:
case ERawImageFormat::G16:
return InFormat; // directly supported
case ERawImageFormat::BGRE8:
case ERawImageFormat::RGBA16F:
case ERawImageFormat::RGBA32F:
case ERawImageFormat::R16F:
case ERawImageFormat::R32F:
return ERawImageFormat::RGBA16; // needs conversion
default:
check(0);
return ERawImageFormat::BGRA8;
};
}
void FPngImageWrapper::Compress(int32 Quality)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FPngImageWrapper::Compress)
if (!CompressedData.Num())
{
//Preserve old single thread code on some platform in relation to a type incompatibility at compile time.
#if PLATFORM_ANDROID
// thread safety
FScopeLock PNGLock(&GPNGSection);
#endif
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_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);
png_bytep* row_pointers = (png_bytep*) png_malloc( png_ptr, Height*sizeof(png_bytep) );
PNGWriteGuard PNGGuard(&png_ptr, &info_ptr);
PNGGuard.SetRowPointers( row_pointers );
// Store the current stack pointer in the jump buffer. setjmp will return non-zero in the case of a write error.
#if PLATFORM_ANDROID
//Preserve old single thread code on some platform in relation to a type incompatibility at compile time.
if (setjmp(SetjmpBuffer) != 0)
#else
//Use libPNG jump buffer solution to allow concurrent compression\decompression on concurrent threads.
if (setjmp(png_jmpbuf(png_ptr)) != 0)
#endif
{
CompressedData.Empty();
UE_LOG(LogImageWrapper, Error, TEXT("PNG Compress Error"));
return;
}
// ---------------------------------------------------------------------------------------------------------
// Anything allocated on the stack after this point will not be destructed correctly in the case of an error
{
int ZlibLevel = 3; // default
// Quality == 0 is the default argument, does not set a zlib level
if ( Quality != 0 )
{
if ( Quality == (int32)EImageCompressionQuality::Uncompressed )
{
ZlibLevel = 0; // compression off
}
else if ( -Quality >= 1 && -Quality <= 9 )
{
// negative quality for zlib level
ZlibLevel = -Quality;
}
else if ( Quality >= 20 && Quality <= 100 )
{
// JPEG quality, just ignore
// calls to GetCompressed(100) are common
}
else
{
UE_LOG(LogImageWrapper, Warning, TEXT("PNG Quality ZlibLevel out of range %d"), Quality );
}
}
PNGGuard.MetadataKeyValuePairs.Reserve(TextBlocks.Num());
PNGGuard.MetadataStrings.Reserve(TextBlocks.Num() * 2);
for (auto& Block : TextBlocks)
{
png_text info {};
info.compression = PNG_TEXT_COMPRESSION_NONE;
PNGGuard.MetadataStrings.Emplace((*Block.Key));
info.key = (char*)*PNGGuard.MetadataStrings.Last();
PNGGuard.MetadataStrings.Emplace((*Block.Value));
info.text = (char*)*PNGGuard.MetadataStrings.Last();
PNGGuard.MetadataKeyValuePairs.Add(info);
}
png_set_text(png_ptr, info_ptr, PNGGuard.MetadataKeyValuePairs.GetData(), PNGGuard.MetadataKeyValuePairs.Num());
png_set_compression_level(png_ptr, ZlibLevel);
png_set_IHDR(png_ptr, info_ptr, Width, Height, BitDepth, (Format == 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);
// If we're writing an uncompressed PNG, then we're expecting to be compressing
// externally and we can assume we're interested in speed. In this case, we force
// the fastest filter to avoid things like Paeth. This is ~2x speedup in decompressing
// bulk data encoded in this manner.
if (Quality == (int32)EImageCompressionQuality::Uncompressed)
{
png_set_filter(png_ptr, 0, PNG_FILTER_UP | PNG_FILTER_VALUE_UP);
}
const uint64 PixelChannels = (Format == ERGBFormat::Gray) ? 1 : 4;
const uint64 BytesPerPixel = (BitDepth * PixelChannels) / 8;
const uint64 BytesPerRow = BytesPerPixel * Width;
for (int64 i = 0; i < Height; i++)
{
row_pointers[i]= &RawData[i * BytesPerRow];
}
png_set_rows(png_ptr, info_ptr, row_pointers);
uint32 Transform = (Format == 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
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, int64 InCompressedSize)
{
if ( ! FImageWrapperBase::SetCompressed(InCompressedData, InCompressedSize) )
{
return false;
}
if ( ! LoadPNGHeader() )
{
return false;
}
if ((BitDepth == 1 || BitDepth == 2 || BitDepth == 4) && ((ColorType & PNG_COLOR_MASK_ALPHA) == 0))
{
// PNG specfication:
// (http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html)
if ((ColorType == PNG_COLOR_TYPE_PALETTE) || (ColorType == PNG_COLOR_TYPE_GRAY))
{
//From png specification:
// Note that the palette uses 8 bits (1 byte) per sample regardless of the image bit depth specification.
// In particular, the palette is 8 bits deep even when it is a suggested quantization of a 16-bit truecolor image.
// ColorType == PNG_COLOR_TYPE_PALETTE supported via:
// png_set_palette_to_rgb (called in UncompressPNGData)
// ColorType == PNG_COLOR_TYPE_GRAYsupported via:
// png_set_expand_gray_1_2_4_to_8 (called in UncompressPNGData)
if (!RawData.Num())
{
check(CompressedData.Num());
UncompressPNGData(Format, 8);
// after UncompressPNGData , BitDepth is now changed to 8
}
}
else if (ColorType == PNG_COLOR_TYPE_RGB)
{
//according to png specification this is not a possiblity
}
}
if ( (Format == ERGBFormat::BGRA || Format == ERGBFormat::RGBA || Format == ERGBFormat::Gray) &&
(BitDepth == 8 || BitDepth == 16) )
{
if ( ! FImageCoreUtils::IsImageImportPossible(Width,Height) )
{
SetError(TEXT("Image dimensions are not possible to import"));
return false;
}
return true;
}
else
{
// Other formats unsupported at present
UE_LOG(LogImageWrapper, Warning, TEXT("PNG Unsupported Format"));
return false;
}
}
void FPngImageWrapper::Uncompress(const ERGBFormat InFormat, const int32 InBitDepth)
{
if(!RawData.Num() || InFormat != Format || InBitDepth != BitDepth)
{
check(CompressedData.Num());
UncompressPNGData(InFormat, InBitDepth);
}
}
void FPngImageWrapper::UncompressPNGData(const ERGBFormat InFormat, const int32 InBitDepth)
{
//Preserve old single thread code on some platform in relation to a type incompatibility at compile time.
#if PLATFORM_ANDROID
// thread safety
FScopeLock PNGLock(&GPNGSection);
#endif
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 == 1 || InBitDepth == 2 || InBitDepth == 4 || InBitDepth == 8 || InBitDepth == 16); // Other formats unsupported at present
// Check if LibPNG can handle the size
if (Height > PNG_UINT_32_MAX / sizeof(png_bytep))
{
RawData.Empty();
UE_LOG(LogImageWrapper, Error, TEXT("PNG image too high to process with libPNG"));
return;
}
// 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
{
png_bytep* row_pointers = (png_bytep*) png_malloc( png_ptr, Height*sizeof(png_bytep) );
PNGReadGuard PNGGuard( &png_ptr, &info_ptr );
PNGGuard.SetRowPointers(row_pointers);
// Store the current stack pointer in the jump buffer. setjmp will return non-zero in the case of a read error.
#if PLATFORM_ANDROID
//Preserve old single thread code on some platform in relation to a type incompatibility at compile time.
if (setjmp(SetjmpBuffer) != 0)
#else
//Use libPNG jump buffer solution to allow concurrent compression\decompression on concurrent threads.
if (setjmp(png_jmpbuf(png_ptr)) != 0)
#endif
{
RawData.Empty();
UE_LOG(LogImageWrapper, Error, TEXT("PNG Decompress Error"));
return;
}
// ---------------------------------------------------------------------------------------------------------
// Anything allocated on the stack after this point will not be destructed correctly in the case of an error
{
png_set_read_fn(png_ptr, this, FPngImageWrapper::user_read_compressed);
// Read the header
png_read_info(png_ptr, info_ptr);
// We want paletted data expanded out to RGB and transparencies turned into alpha
png_set_expand(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))
{
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 uint64 PixelChannels = (InFormat == ERGBFormat::Gray) ? 1 : 4;
const uint64 BytesPerPixel = (InBitDepth * PixelChannels) / 8;
check(BytesPerPixel > 0);
const int64 BytesPerRow = BytesPerPixel * Width;
check(Height <= MAX_int64 / BytesPerRow); // check for overflow on multiplication
const int64 TotalBytes = Height * BytesPerRow;
RawData.Empty(TotalBytes);
RawData.AddUninitialized(TotalBytes);
for (int64 i = 0; i < Height; i++)
{
row_pointers[i]= &RawData[i * BytesPerRow];
}
png_set_rows(png_ptr, info_ptr, row_pointers);
// PNG files are RGBA, if we want BGRA we need to swap
if (InFormat == ERGBFormat::BGRA)
{
png_set_bgr(png_ptr);
}
// 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)
{
png_set_swap(png_ptr);
}
#endif
// @todo Oodle : remove all these conversions
// ImageWrappers should load images as they are in the file
// FImage does conversions after loading
// Convert grayscale png to RGB if requested
if ((ColorType & PNG_COLOR_MASK_COLOR) == 0 &&
(InFormat == ERGBFormat::RGBA || InFormat == ERGBFormat::BGRA))
{
png_set_gray_to_rgb(png_ptr);
}
// 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, PNG_ERROR_ACTION_WARN, -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);
png_set_strip_alpha(png_ptr);
}
// Reduce 16-bit to 8-bit if requested
if (BitDepth == 16 && InBitDepth == 8)
{
png_set_scale_16(png_ptr);
}
// Increase 8-bit to 16-bit if requested
if (BitDepth <= 8 && InBitDepth == 16)
{
png_set_expand_16(png_ptr);
}
// Finalize init
(void)png_set_interlace_handling(png_ptr);
png_read_update_info(png_ptr, info_ptr);
// Read the PNG
png_read_image(png_ptr, row_pointers);
png_read_end(png_ptr, info_ptr);
}
}
#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("FPNGImageCRCError: %s"), *e.ErrorText);
}
#endif
Format = InFormat;
BitDepth = 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())
{
#if PLATFORM_ANDROID
// thread safety
FScopeLock PNGLock(&GPNGSection);
#endif
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);
// Store the current stack pointer in the jump buffer. setjmp will return non-zero in the case of a read error.
#if PLATFORM_ANDROID
//Preserve old single thread code on some platform in relation to a type incompatibility at compile time.
if (setjmp(SetjmpBuffer) != 0)
#else
//Use libPNG jump buffer solution to allow concurrent compression\decompression on concurrent threads.
if (setjmp(png_jmpbuf(png_ptr)) != 0)
#endif
{
UE_LOG(LogImageWrapper, Error, TEXT("PNG Header Error"));
return false;
}
// ---------------------------------------------------------------------------------------------------------
// Anything allocated on the stack after this point will not be destructed correctly in the case of an error
#if !PLATFORM_EXCEPTIONS_DISABLED
try
#endif
{
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;
if (info_ptr->valid & PNG_INFO_tRNS)
{
Format = ERGBFormat::RGBA;
}
else
{
Format = (ColorType & PNG_COLOR_MASK_COLOR || ColorType & PNG_COLOR_MASK_ALPHA) ? ERGBFormat::RGBA : ERGBFormat::Gray;
}
if (Format == ERGBFormat::RGBA && BitDepth <= 8)
{
Format = ERGBFormat::BGRA;
}
// Read all text blocks into our map
TextBlocks.Reset();
png_textp TextPointers = nullptr;
int32 NumTextBlocks = 0;
if (png_get_text(png_ptr, info_ptr, &TextPointers, &NumTextBlocks) != 0)
{
for (int32 Index = 0; Index < NumTextBlocks; Index++)
{
TextBlocks.Add(TextPointers[Index].key, TextPointers[Index].text);
}
}
}
#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("FPNGImageCRCError: %s"), *e.ErrorText);
return false;
}
#endif
return true;
}
return false;
}
bool FPngImageWrapper::SupportsMetadata() const
{
return true;
}
void FPngImageWrapper::AddMetadata(const FString& InKey, const FString& InValue)
{
TextBlocks.Add(InKey, InValue);
}
bool FPngImageWrapper::TryGetMetadata(const FString& InKey, FString& OutValue) const
{
if (const FString* Value = TextBlocks.Find(InKey))
{
OutValue = *Value;
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);
if (IntFitsIn<int64>(length) == false)
{
UE_LOG(LogImageWrapper, Warning, TEXT("Bad PNG read length: %llu"), length);
ctx->SetError(TEXT("Invalid length in read_compressed")); // this doesn't seem to get logged on failure.
return;
}
FGuardedInt64 GuardedEndOffset = FGuardedInt64(ctx->ReadOffset) + length;
if (GuardedEndOffset.InvalidOrGreaterThan(ctx->CompressedData.Num()))
{
UE_LOG(LogImageWrapper, Warning, TEXT("Bad PNG read position: offset %" INT64_FMT " num %lld length: %llu"), ctx->ReadOffset, ctx->CompressedData.Num(), length);
ctx->SetError(TEXT("Invalid read position for CompressedData.")); // this doesn't seem to get logged on failure.
return;
}
FMemory::Memcpy(data, &ctx->CompressedData[ctx->ReadOffset], length);
ctx->ReadOffset = GuardedEndOffset.Get(0);
}
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);
int64 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_error_ptr(png_ptr);
{
FString ErrorMsg = ANSI_TO_TCHAR(error_msg);
ctx->SetError(*ErrorMsg);
UE_LOG(LogImageWrapper, Error, TEXT("PNG Error(%s): %s"), ctx->DebugImageName != nullptr ? ctx->DebugImageName : TEXT(""), *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
#if PLATFORM_ANDROID
//Preserve old single thread code on some platform in relation to a type incompatibility at compile time.
//The other platforms use libPNG jump buffer solution to allow concurrent compression\decompression on concurrent threads. The jump is trigered in libPNG after this function returns.
longjmp(ctx->SetjmpBuffer, 1);
#endif
}
void FPngImageWrapper::user_warning_fn(png_structp png_ptr, png_const_charp warning_msg)
{
FPngImageWrapper* ctx = (FPngImageWrapper*)png_get_error_ptr(png_ptr);
const TCHAR* LocalDebugImageName = ctx != nullptr ? ctx->DebugImageName : nullptr;
// libPNG 1.6+ has some very spammy warnings about incorrect color profile metadata that is unfortunately
// extremely common in the wild, so manually detect and downgrade the verbosity of that
if (FPlatformString::Strncmp(warning_msg, "iCCP:", 5) == 0)
{
UE_LOG(LogImageWrapper, Verbose, TEXT("PNG Warning(%s) %s"), LocalDebugImageName != nullptr ? LocalDebugImageName : TEXT(""), ANSI_TO_TCHAR(warning_msg));
}
else
{
UE_LOG(LogImageWrapper, Warning, TEXT("PNG Warning(%s) %s"), LocalDebugImageName != nullptr ? LocalDebugImageName : TEXT(""), 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"
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif //WITH_UNREALPNG