// Copyright Epic Games, Inc. All Rights Reserved. #include "ImageWrapperPrivate.h" #include "CoreTypes.h" #include "Modules/ModuleManager.h" #include "Formats/BmpImageWrapper.h" #include "Formats/ExrImageWrapper.h" #include "Formats/HdrImageWrapper.h" #include "Formats/IcnsImageWrapper.h" #include "Formats/IcoImageWrapper.h" #include "Formats/JpegImageWrapper.h" #include "Formats/PngImageWrapper.h" #include "Formats/TgaImageWrapper.h" #include "Formats/TiffImageWrapper.h" #include "Formats/DdsImageWrapper.h" #include "DDSFile.h" #include "IImageWrapperModule.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}; // Binary for #?RADIANCE static const uint8 IMAGE_MAGIC_HDR[] = {0x23, 0x3f, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x0a}; // Binary for #?RGBE static const uint8 IMAGE_MAGIC_HDR2[] = {0x23, 0x3f, 0x52, 0x47, 0x42, 0x45 }; // Tiff has two magic bytes sequence static const uint8 IMAGE_MAGIC_TIFF_LITTLE_ENDIAN[] = {0x49, 0x49, 0x2A, 0x00}; static const uint8 IMAGE_MAGIC_TIFF_BIG_ENDIAN[] = {0x4D, 0x4D, 0x00, 0x2A}; /** Internal helper function to verify image signature. */ template bool StartsWith(const uint8* Content, int64 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 TSharedPtr CreateImageWrapper(const EImageFormat InFormat) override { TRACE_CPUPROFILER_EVENT_SCOPE(ImageWrapper.Create); TSharedPtr ImageWrapper; // Allocate a helper for the format type switch(InFormat) { #if WITH_UNREALPNG case EImageFormat::PNG: ImageWrapper = MakeShared(); break; #endif // WITH_UNREALPNG #if WITH_UNREALJPEG case EImageFormat::JPEG: ImageWrapper = MakeShared(); break; case EImageFormat::GrayscaleJPEG: ImageWrapper = MakeShared(1); break; #endif //WITH_UNREALJPEG case EImageFormat::BMP: ImageWrapper = MakeShared(); break; case EImageFormat::ICO: ImageWrapper = MakeShared(); break; #if WITH_UNREALEXR case EImageFormat::EXR: ImageWrapper = MakeShared(); break; #endif case EImageFormat::ICNS: ImageWrapper = MakeShared(); break; case EImageFormat::TGA: ImageWrapper = MakeShared(); break; case EImageFormat::HDR: ImageWrapper = MakeShared(); break; #if WITH_LIBTIFF case EImageFormat::TIFF: ImageWrapper = MakeShared(); break; #endif // WITH_LIBTIFF case EImageFormat::DDS: ImageWrapper = MakeShared(); break; default: break; } return ImageWrapper; } virtual EImageFormat DetectImageFormat(const void* CompressedData, int64 CompressedSize) override { EImageFormat 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; } else if (StartsWith((uint8*)CompressedData, CompressedSize, IMAGE_MAGIC_HDR) || StartsWith((uint8*)CompressedData, CompressedSize, IMAGE_MAGIC_HDR2)) { Format = EImageFormat::HDR; } else if (StartsWith((uint8*)CompressedData, CompressedSize, IMAGE_MAGIC_TIFF_LITTLE_ENDIAN)) { Format = EImageFormat::TIFF; } else if (StartsWith((uint8*)CompressedData, CompressedSize, IMAGE_MAGIC_TIFF_BIG_ENDIAN)) { Format = EImageFormat::TIFF; } else if ( FTgaImageWrapper::IsTGAHeader(CompressedData, CompressedSize) ) { Format = EImageFormat::TGA; } else if ( UE::DDS::FDDSFile::IsADDS((const uint8 *)CompressedData, CompressedSize) ) { Format = EImageFormat::DDS; } return Format; } virtual const TCHAR * GetExtension(EImageFormat Format) override { switch(Format) { case EImageFormat::PNG: return TEXT("png"); case EImageFormat::JPEG: return TEXT("jpg"); case EImageFormat::GrayscaleJPEG: return TEXT("jpg"); case EImageFormat::BMP: return TEXT("bmp"); case EImageFormat::ICO: return TEXT("ico"); case EImageFormat::EXR: return TEXT("exr"); case EImageFormat::ICNS: return TEXT("icns"); case EImageFormat::TGA: return TEXT("tga"); case EImageFormat::HDR: return TEXT("hdr"); case EImageFormat::TIFF: return TEXT("tiff"); case EImageFormat::DDS: return TEXT("dds"); case EImageFormat::Invalid: default: check(0); return nullptr; } } virtual EImageFormat GetImageFormatFromExtension(const TCHAR * Name) override { const TCHAR * Dot = FCString::Strrchr(Name,TEXT('.')); if ( Dot ) { Name = Dot+1; } if ( FCString::Stricmp(Name,TEXT("png")) == 0 ) { return EImageFormat::PNG; } else if ( FCString::Stricmp(Name,TEXT("jpg")) == 0 || FCString::Stricmp(Name,TEXT("jpeg")) == 0 ) { return EImageFormat::JPEG; } else if ( FCString::Stricmp(Name,TEXT("bmp")) == 0 ) { return EImageFormat::BMP; } else if ( FCString::Stricmp(Name,TEXT("ico")) == 0 ) { return EImageFormat::ICO; } else if ( FCString::Stricmp(Name,TEXT("exr")) == 0 ) { return EImageFormat::EXR; } else if ( FCString::Stricmp(Name,TEXT("icns")) == 0 ) { return EImageFormat::ICNS; } else if ( FCString::Stricmp(Name,TEXT("tga")) == 0 ) { return EImageFormat::TGA; } else if ( FCString::Stricmp(Name,TEXT("hdr")) == 0 ) { return EImageFormat::HDR; } else if ( FCString::Stricmp(Name,TEXT("tiff")) == 0 || FCString::Stricmp(Name,TEXT("tif")) == 0 ) { return EImageFormat::TIFF; } else if ( FCString::Stricmp(Name,TEXT("dds")) == 0 ) { return EImageFormat::DDS; } else { UE_LOG(LogImageWrapper,Warning,TEXT("GetImageFormatFromExtension not found : %s\n"),Name); return EImageFormat::Invalid; } } virtual ERawImageFormat::Type ConvertRGBFormat(ERGBFormat RGBFormat,int BitDepth,bool * bIsExactMatch) override { return IImageWrapper::ConvertRGBFormat(RGBFormat,BitDepth,bIsExactMatch); } virtual void ConvertRawImageFormat(ERawImageFormat::Type RawFormat, ERGBFormat & OutFormat,int & OutBitDepth) override { IImageWrapper::ConvertRawImageFormat(RawFormat,OutFormat,OutBitDepth); } virtual EImageFormat GetDefaultOutputFormat(ERawImageFormat::Type RawFormat) override { switch(RawFormat) { case ERawImageFormat::G8: case ERawImageFormat::BGRA8: case ERawImageFormat::RGBA16: case ERawImageFormat::G16: return EImageFormat::PNG; case ERawImageFormat::BGRE8: return EImageFormat::HDR; case ERawImageFormat::RGBA16F: case ERawImageFormat::RGBA32F: case ERawImageFormat::R16F: case ERawImageFormat::R32F: return EImageFormat::EXR; default: check(0); return EImageFormat::Invalid; } } virtual bool DecompressImage(const void* InCompressedData, int64 InCompressedSize, FImage & OutImage) override { TRACE_CPUPROFILER_EVENT_SCOPE(ImageWrapper.Decompress); EImageFormat ImageFormat = DetectImageFormat(InCompressedData,InCompressedSize); if ( ImageFormat == EImageFormat::Invalid ) { return false; } TSharedPtr ImageWrapper = CreateImageWrapper(ImageFormat); if ( ! ImageWrapper.IsValid() ) { return false; } if ( ! ImageWrapper->SetCompressed(InCompressedData,InCompressedSize) ) { return false; } if ( ! ImageWrapper->GetRawImage(OutImage) ) { return false; } return true; } virtual bool CompressImage(TArray64 & OutData, EImageFormat ToFormat, const FImageView & InImage, int32 Quality) override { TRACE_CPUPROFILER_EVENT_SCOPE(ImageWrapper.Compress); TSharedPtr ImageWrapper = CreateImageWrapper(ToFormat); if ( ! ImageWrapper.IsValid() ) { return false; } ERGBFormat RGBFormat; int BitDepth; ConvertRawImageFormat(InImage.Format,RGBFormat,BitDepth); // can't save slices : check(InImage.NumSlices == 1 ); FImageView WriteImage = InImage; FImage TempImage; if ( ! ImageWrapper->CanSetRawFormat(RGBFormat,BitDepth) ) { // output image format may not support this pixel format // in that case conversion is needed ERawImageFormat::Type NewFormat = ImageWrapper->GetSupportedRawFormat(InImage.Format); check( NewFormat != InImage.Format ); // we will write to the image wrapper using "NewFormat" // do a blit to convert InImage to NewFormat TempImage.Init(InImage.SizeX,InImage.SizeY,NewFormat,ERawImageFormat::GetDefaultGammaSpace(NewFormat)); WriteImage = TempImage; FImageCore::CopyImage(InImage,WriteImage); // re-get the RGBFormat we will set : ConvertRawImageFormat(NewFormat,RGBFormat,BitDepth); check( ImageWrapper->CanSetRawFormat(RGBFormat,BitDepth) ); } else if ( InImage.GetGammaSpace() != ERawImageFormat::GetDefaultGammaSpace(InImage.Format) ) { // ImageWrapper SetRaw() does not accept gamma information // it assumes U8 = SRGB and Float = Linear // eg. if you save a U8 surface with linear gamma // go ahead and write the U8 bytes without changing them // I think that's probably what is intended // when someone writes Linear U8 to PNG they don't want me to do a gamma transform // in some cases we could write the gamma setting to the output when possible, eg. for DDS output // not doing that for now } if ( ! ImageWrapper->SetRaw(WriteImage.RawData,WriteImage.GetImageSizeBytes(),WriteImage.SizeX,WriteImage.SizeY,RGBFormat,BitDepth) ) { return false; } OutData = ImageWrapper->GetCompressed(Quality); if ( OutData.IsEmpty() ) { return false; } return true; } public: //~ IModuleInterface interface virtual void StartupModule() override { } virtual void ShutdownModule() override { } }; IMPLEMENT_MODULE(FImageWrapperModule, ImageWrapper);