Files
UnrealEngineUWP/Engine/Source/Editor/LandscapeEditor/Private/LandscapeFileFormatPng.cpp
Julien StJean 547c7f160d Optimisation to the legacy texture import and improvement to texture wrapper
Minor change to FImageWrapperBase. GetRaw and GetCompressed now consume the array with the same name instead of having to do a copy of it.
I changed the api IImageWrapper::GetCompressed to return a TArray64<uint8> instead of returning a const TArray64<uint8>&.
Added the format RGBAF to the struct ERGBFormat. Changed the engine code using the EXR image wrapper to reflect that.
The EXR image wrapper now avoid doing an unessary copy of the compressed image when calling compress.

Improvement to the performence of the function UTextureFactory::ImportImage. We now use the magic bytes of the file for certains format to skip some tests.

Here is some performance metrics I captured on my desktop (6 core, 12 threads XEON)

Importing a folder of tiff files (22 files, 4.16 GB Total)
Before: 66.152738 seconds
After: 43.609245 seconds

#jira UEENT-3822
#rb Alexis.Matte

[CL 16128765 by Julien StJean in ue5-main branch]
2021-04-27 11:59:02 -04:00

253 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LandscapeFileFormatPng.h"
#include "Misc/FileHelper.h"
#include "Modules/ModuleManager.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#include "Algo/Transform.h"
#define LOCTEXT_NAMESPACE "LandscapeEditor.NewLandscape"
FLandscapeHeightmapFileFormat_Png::FLandscapeHeightmapFileFormat_Png()
{
FileTypeInfo.Description = LOCTEXT("FileFormatPng_HeightmapDesc", "Heightmap .png files");
FileTypeInfo.Extensions.Add(".png");
FileTypeInfo.bSupportsExport = true;
}
FLandscapeFileInfo FLandscapeHeightmapFileFormat_Png::Validate(const TCHAR* HeightmapFilename, FName LayerName) const
{
FLandscapeFileInfo Result;
TArray64<uint8> ImportData;
if (!FFileHelper::LoadFileToArray(ImportData, HeightmapFilename, FILEREAD_Silent))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileReadError", "Error reading heightmap file");
}
else
{
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper");
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
if (!ImageWrapper->SetCompressed(ImportData.GetData(), ImportData.Num()) || ImageWrapper->GetWidth() <= 0 || ImageWrapper->GetHeight() <= 0)
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileCorruptPng", "The heightmap file cannot be read (corrupt png?)");
}
else
{
if (ImageWrapper->GetFormat() != ERGBFormat::Gray)
{
Result.ResultCode = ELandscapeImportResult::Warning;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileColorPng", "The heightmap file appears to be a color png, grayscale is expected. The import *can* continue, but the result may not be what you expect...");
}
else if (ImageWrapper->GetBitDepth() != 16)
{
Result.ResultCode = ELandscapeImportResult::Warning;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileLowBitDepth", "The heightmap file appears to be an 8-bit png, 16-bit is preferred. The import *can* continue, but the result may be lower quality than desired.");
}
FLandscapeFileResolution ImportResolution;
ImportResolution.Width = ImageWrapper->GetWidth();
ImportResolution.Height = ImageWrapper->GetHeight();
Result.PossibleResolutions.Add(ImportResolution);
}
}
// possible todo: support sCAL (XY scale) and pCAL (Z scale) png chunks for filling out Result.DataScale
// I don't know if any heightmap generation software uses these or not
// if we support their import we should make the exporter write them too
return Result;
}
FLandscapeImportData<uint16> FLandscapeHeightmapFileFormat_Png::Import(const TCHAR* HeightmapFilename, FName LayerName, FLandscapeFileResolution ExpectedResolution) const
{
FLandscapeImportData<uint16> Result;
TArray<uint8> TempData;
if (!FFileHelper::LoadFileToArray(TempData, HeightmapFilename, FILEREAD_Silent))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileReadError", "Error reading heightmap file");
}
else
{
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper");
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
if (!ImageWrapper->SetCompressed(TempData.GetData(), TempData.Num()))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileCorruptPng", "The heightmap file cannot be read (corrupt png?)");
}
else if (ImageWrapper->GetWidth() != ExpectedResolution.Width || ImageWrapper->GetHeight() != ExpectedResolution.Height)
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_HeightmapResolutionMismatch", "The heightmap file's resolution does not match the requested resolution");
}
else
{
if (ImageWrapper->GetFormat() != ERGBFormat::Gray)
{
Result.ResultCode = ELandscapeImportResult::Warning;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileColorPng", "The heightmap file appears to be a color png, grayscale is expected. The import *can* continue, but the result may not be what you expect...");
}
else if (ImageWrapper->GetBitDepth() != 16)
{
Result.ResultCode = ELandscapeImportResult::Warning;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileLowBitDepth", "The heightmap file appears to be an 8-bit png, 16-bit is preferred. The import *can* continue, but the result may be lower quality than desired.");
}
TArray64<uint8> RawData;
if (ImageWrapper->GetBitDepth() <= 8)
{
if (!ImageWrapper->GetRaw(ERGBFormat::Gray, 8, RawData))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileCorruptPng", "The heightmap file cannot be read (corrupt png?)");
}
else
{
Result.Data.Empty(ExpectedResolution.Width * ExpectedResolution.Height);
Algo::Transform(RawData, Result.Data, [](uint8 Value) { return Value * 0x101; }); // Expand to 16-bit
}
}
else
{
if (!ImageWrapper->GetRaw(ERGBFormat::Gray, 16, RawData))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_HeightmapFileCorruptPng", "The heightmap file cannot be read (corrupt png?)");
}
else
{
Result.Data.Empty(ExpectedResolution.Width * ExpectedResolution.Height);
Result.Data.AddUninitialized(ExpectedResolution.Width * ExpectedResolution.Height);
FMemory::Memcpy(Result.Data.GetData(), RawData.GetData(), ExpectedResolution.Width * ExpectedResolution.Height * 2);
}
}
}
}
return Result;
}
void FLandscapeHeightmapFileFormat_Png::Export(const TCHAR* HeightmapFilename, FName LayerName, TArrayView<const uint16> Data, FLandscapeFileResolution DataResolution, FVector Scale) const
{
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper");
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
if (ImageWrapper->SetRaw(Data.GetData(), Data.Num() * 2, DataResolution.Width, DataResolution.Height, ERGBFormat::Gray, 16))
{
const TArray64<uint8> TempData = ImageWrapper->GetCompressed();
FFileHelper::SaveArrayToFile(TempData, HeightmapFilename);
}
}
//////////////////////////////////////////////////////////////////////////
FLandscapeWeightmapFileFormat_Png::FLandscapeWeightmapFileFormat_Png()
{
FileTypeInfo.Description = LOCTEXT("FileFormatPng_WeightmapDesc", "Layer .png files");
FileTypeInfo.Extensions.Add(".png");
FileTypeInfo.bSupportsExport = true;
}
FLandscapeFileInfo FLandscapeWeightmapFileFormat_Png::Validate(const TCHAR* WeightmapFilename, FName LayerName) const
{
FLandscapeFileInfo Result;
TArray64<uint8> ImportData;
if (!FFileHelper::LoadFileToArray(ImportData, WeightmapFilename, FILEREAD_Silent))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_LayerFileReadError", "Error reading layer file");
}
else
{
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper");
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
if (!ImageWrapper->SetCompressed(ImportData.GetData(), ImportData.Num()))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_LayerCorruptPng", "The layer file cannot be read (corrupt png?)");
}
else
{
if (ImageWrapper->GetFormat() != ERGBFormat::Gray)
{
Result.ResultCode = ELandscapeImportResult::Warning;
Result.ErrorMessage = LOCTEXT("Import_LayerColorPng", "The layer file appears to be a color png, grayscale is expected. The import *can* continue, but the result may not be what you expect...");
}
FLandscapeFileResolution ImportResolution;
ImportResolution.Width = ImageWrapper->GetWidth();
ImportResolution.Height = ImageWrapper->GetHeight();
Result.PossibleResolutions.Add(ImportResolution);
}
}
return Result;
}
FLandscapeImportData<uint8> FLandscapeWeightmapFileFormat_Png::Import(const TCHAR* WeightmapFilename, FName LayerName, FLandscapeFileResolution ExpectedResolution) const
{
FLandscapeImportData<uint8> Result;
TArray64<uint8> TempData;
if (!FFileHelper::LoadFileToArray(TempData, WeightmapFilename, FILEREAD_Silent))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_LayerFileReadError", "Error reading layer file");
}
else
{
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper");
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
if (!ImageWrapper->SetCompressed(TempData.GetData(), TempData.Num()))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_LayerCorruptPng", "The layer file cannot be read (corrupt png?)");
}
else if (ImageWrapper->GetWidth() != ExpectedResolution.Width || ImageWrapper->GetHeight() != ExpectedResolution.Height)
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_LayerResolutionMismatch", "The layer file's resolution does not match the requested resolution");
}
else if (!ImageWrapper->GetRaw(ERGBFormat::Gray, 8, Result.Data))
{
Result.ResultCode = ELandscapeImportResult::Error;
Result.ErrorMessage = LOCTEXT("Import_LayerCorruptPng", "The layer file cannot be read (corrupt png?)");
}
else
{
if (ImageWrapper->GetFormat() != ERGBFormat::Gray)
{
Result.ResultCode = ELandscapeImportResult::Warning;
Result.ErrorMessage = LOCTEXT("Import_LayerColorPng", "The layer file appears to be a color png, grayscale is expected. The import *can* continue, but the result may not be what you expect...");
}
}
}
return Result;
}
void FLandscapeWeightmapFileFormat_Png::Export(const TCHAR* WeightmapFilename, FName LayerName, TArrayView<const uint8> Data, FLandscapeFileResolution DataResolution, FVector Scale) const
{
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper");
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
if (ImageWrapper->SetRaw(Data.GetData(), Data.Num(), DataResolution.Width, DataResolution.Height, ERGBFormat::Gray, 8))
{
const TArray64<uint8> TempData = ImageWrapper->GetCompressed();
FFileHelper::SaveArrayToFile(TempData, WeightmapFilename);
}
}
#undef LOCTEXT_NAMESPACE