读取与导出本地图片
效果
通过代码读取本地的 png 图片(可自己扩展其它类型)转换为 UE4 内部可用的 Texture2D;
利用 UE4 内的 Texture2D 在本地指定目录下生成对应 png 图片;
利用 UE4 内的 Sprite 在本地指定目录下生成对应的 png 图片。
PNG -> Texture2D
分析
读取指定目录的 png 图片,将信息存入数组 RawFileData
用 ImageWrapper 来保存 RawFileData 转为可用信息
生成空的 Texture
将数据写入 Texture 中
保存资源
读取指定目录的 png 图片,将信息存入数组 RawFileData
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const FString PicturePath = ImageDirectory.Path + TEXT ("/" ) + ImageName + TEXT (".png" );if (!FPlatformFileManager::Get ().GetPlatformFile ().FileExists (*PicturePath)){ return nullptr ; } TArray<uint8> RawFileData; if (!FFileHelper::LoadFileToArray (RawFileData, *PicturePath)){ return nullptr ; }
用 ImageWrapper 来保存 RawFileData 转为可用信息
需要依赖模块:ImageWrapper
,包含头文件:IImageWrapper.h
、IImageWrapperModule.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper" ); TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper (EImageFormat::PNG); if (ImageWrapper.IsValid () && ImageWrapper->SetCompressed (RawFileData.GetData (), RawFileData.Num ())){ const TArray<uint8>* UncompressedRGBA = nullptr ; if (ImageWrapper->GetRaw (ERGBFormat::BGRA, 8 , UncompressedRGBA)) { } }
生成空的 Texture
首先我们要确定想要生成的 Texture 所在的位置,也就是要确定 Package 的位置,也就是确定 Path:
Package 的位置由 MountPoint
开始,这个 MountPoint
就是类似 /Game
、/Engine
这样的东西;
如果我们需要 在指定目录保存这些文件 ,比如我们不想在 /Game
、/Engine
的目录写入这些信息,想在自己的插件目录、其它本地目录保存,那么我们需要先注册 MountPoint:
1 2 FPackageName::RegisterMountPoint ("/你想的 MountPoint 名字/" , 对应的路径);
接着我们:
1 2 3 4 5 6 const FString PackageName = MountPointName + TEXT ("/textures/" ) + ImageName;UPackage* TexturePackage = CreatePackage (nullptr , *PackageName); TexturePackage->FullyLoad (); Texture = NewObject<UTexture2D>(TexturePackage, FName (*ImageName), RF_Public | RF_Standalone | RF_MarkAsRootSet);
将数据写入 Texture 中
我们生成的空 Texture 会默认采用 Mipmap 压缩,但是显然这不一定合理。
如果 边长 不是 2 的幂次方 ,则不能使用 Mipmap 压缩,否则会出现错误,我们可以用 x & (x - 1)
来判断 x
是否是 2 的幂次方,如果 x & (x - 1) == 0
则说明是,否则不是 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 int32 SizeX = ImageWrapper->GetWidth (); int32 SizeY = ImageWrapper->GetHeight (); const EPixelFormat PixelFormat = PF_B8G8R8A8;int32 PixelSize = SizeX * SizeY * GPixelFormats[PixelFormat].BlockBytes; if (SizeX > 0 && SizeY > 0 && (SizeX % GPixelFormats[PixelFormat].BlockSizeX) == 0 && (SizeY % GPixelFormats[PixelFormat].BlockSizeY) == 0 ){ Texture->PlatformData = new FTexturePlatformData (); Texture->PlatformData->SizeX = SizeX; Texture->PlatformData->SizeY = SizeY; Texture->PlatformData->NumSlices = 1 ; Texture->PlatformData->PixelFormat = PixelFormat; if ( (SizeX & (SizeX - 1 ) || (SizeY & (SizeY - 1 ))) ) Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; FTexture2DMipMap* Mip = new FTexture2DMipMap (); Texture->PlatformData->Mips.Add (Mip); Mip->SizeX = SizeX; Mip->SizeY = SizeY; Mip->BulkData.Lock (LOCK_READ_WRITE); uint8* TextureData = (uint8*) Mip->BulkData.Realloc (PixelSize); FMemory::Memcpy (TextureData, UncompressedRGBA->GetData (), PixelSize); Mip->BulkData.Unlock (); Texture->Source.Init (SizeX, SizeY, 1 , 1 , ETextureSourceFormat::TSF_BGRA8, UncompressedRGBA->GetData ()); Texture->UpdateResource (); }
保存资源
1 2 3 4 5 6 7 8 9 TexturePackage->MarkPackageDirty (); FAssetRegistryModule::AssetCreated (Texture); FString PackageFileName = FPackageName::LongPackageNameToFilename (PackageName, FPackageName::GetAssetPackageExtension ()); UPackage::SavePackage (TexturePackage, Texture, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *PackageFileName);
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 UTexture2D* CreateTexture () { UTexture2D* Texture = nullptr ; const FString PicturePath = ImageDirectory.Path + TEXT ("/" ) + ImageName + TEXT (".png" ); if (!FPlatformFileManager::Get ().GetPlatformFile ().FileExists (*PicturePath)) { return nullptr ; } const FString PackageName = MountPointName + TEXT ("/textures/" ) + ImageName; TArray<uint8> RawFileData; if (!FFileHelper::LoadFileToArray (RawFileData, *PicturePath)) { return nullptr ; } IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>("ImageWrapper" ); TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper (EImageFormat::PNG); if (ImageWrapper.IsValid () && ImageWrapper->SetCompressed (RawFileData.GetData (), RawFileData.Num ())) { const TArray<uint8>* UncompressedRGBA = nullptr ; if (ImageWrapper->GetRaw (ERGBFormat::BGRA, 8 , UncompressedRGBA)) { int32 SizeX = ImageWrapper->GetWidth (); int32 SizeY = ImageWrapper->GetHeight (); const EPixelFormat PixelFormat = PF_B8G8R8A8; int32 PixelSize = SizeX * SizeY * GPixelFormats[PixelFormat].BlockBytes; if (SizeX > 0 && SizeY > 0 && (SizeX % GPixelFormats[PixelFormat].BlockSizeX) == 0 && (SizeY % GPixelFormats[PixelFormat].BlockSizeY) == 0 ) { UPackage* TexturePackage = CreatePackage (nullptr , *PackageName); TexturePackage->FullyLoad (); Texture = NewObject<UTexture2D>(TexturePackage, FName (*ImageName), RF_Public | RF_Standalone | RF_MarkAsRootSet); Texture->PlatformData = new FTexturePlatformData (); Texture->PlatformData->SizeX = SizeX; Texture->PlatformData->SizeY = SizeY; Texture->PlatformData->NumSlices = 1 ; Texture->PlatformData->PixelFormat = PixelFormat; if ( (SizeX & (SizeX - 1 ) || (SizeY & (SizeY - 1 ))) ) Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; FTexture2DMipMap* Mip = new FTexture2DMipMap (); Texture->PlatformData->Mips.Add (Mip); Mip->SizeX = SizeX; Mip->SizeY = SizeY; Mip->BulkData.Lock (LOCK_READ_WRITE); uint8* TextureData = (uint8*) Mip->BulkData.Realloc (PixelSize); FMemory::Memcpy (TextureData, UncompressedRGBA->GetData (), PixelSize); Mip->BulkData.Unlock (); Texture->Source.Init (SizeX, SizeY, 1 , 1 , ETextureSourceFormat::TSF_BGRA8, UncompressedRGBA->GetData ()); Texture->UpdateResource (); TexturePackage->MarkPackageDirty (); FAssetRegistryModule::AssetCreated (Texture); FString PackageFileName = FPackageName::LongPackageNameToFilename (PackageName, FPackageName::GetAssetPackageExtension ()); UPackage::SavePackage (TexturePackage, Texture, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *PackageFileName); } } } return Texture; }
参考源码
EditorFactories : UTextureFactory::ImportTexture()
:实现了从外部导入图片生成 Texture
Texture2D : UTexture2D::CreateTransient()
:实现了生成一个临时性的 Texture2D 文件
Texture : FTextureSource::GetMipData()
:实现了获取 Texture 中 Mip 的数据
Texture2D -> PNG
分析
修改 Texture 的设置
读取像素信息并生成图片
修改 Texture 的设置
在原本 Texture 的设置下,我们可能无法从 BulkData 读取出信息,会返回一个空指针,所以我们需要先修改一下 Texture 的设置;同时记录一下原本的设置,用于之后的复原。
CompressionSettings 设置为 TC_VectorDisplacementmap
MipGenSettings 设置为 TMGS_NoMipmaps
SRGB 设置为 false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 TextureCompressionSettings OldCompressionSettings = Texture->CompressionSettings; TextureMipGenSettings OldMipGenSettings = Texture->MipGenSettings; bool OldSRGB = Texture->SRGB;Texture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap; Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; Texture->SRGB = false ; Texture->UpdateResource (); Texture->CompressionSettings = OldCompressionSettings; Texture->MipGenSettings = OldMipGenSettings; Texture->SRGB = OldSRGB; Texture->UpdateResource ();
读取像素信息并生成图片
我们从 Texture 的 Platform 中读取出 Mip 的 BulkData,然后对其进行映射,将信息映射到 PixelColor 中
每一个 PixelColor 包含着 BGRA 的信息,多个 PixelColor 组成了数组 ColorData;
接着依赖于 ImageUtils
,我们压缩这个 ColorData 生成 ImageData,并将其写入指定位置,就可以生成出 png 图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 FTexture2DMipMap& Mip = Texture->PlatformData->Mips[0 ]; uint8* TextureData = (uint8*) Mip.BulkData.Lock (LOCK_READ_WRITE); Mip.BulkData.Unlock (); int32 SizeX = Texture->PlatformData->SizeX; int32 SizeY = Texture->PlatformData->SizeY; TArray<FColor> ColorData; for (int32 IndexY = 0 ; IndexY < SizeY; IndexY++){ for (int32 IndexX = 0 ; IndexX < SizeX; IndexX++) { FColor PixelColor; PixelColor.B = TextureData[(IndexY * SizeX + IndexX) * 4 + 0 ]; PixelColor.G = TextureData[(IndexY * SizeX + IndexX) * 4 + 1 ]; PixelColor.R = TextureData[(IndexY * SizeX + IndexX) * 4 + 2 ]; PixelColor.A = TextureData[(IndexY * SizeX + IndexX) * 4 + 3 ]; ColorData.Add (PixelColor); } } TArray<uint8> ImageData; FImageUtils::CompressImageArray (SizeX, SizeY, ColorData, ImageData); FFileHelper::SaveArrayToFile (ImageData, *PicturePath);
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 void WriteTexture (UTexture2D* Texture) { FString PicturePath = ExportImageDirectory.Path + TEXT ("/" ) + Texture->GetName () + TEXT (".png" ); TextureCompressionSettings OldCompressionSettings = Texture->CompressionSettings; TextureMipGenSettings OldMipGenSettings = Texture->MipGenSettings; bool OldSRGB = Texture->SRGB; Texture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap; Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; Texture->SRGB = false ; Texture->UpdateResource (); FTexture2DMipMap& Mip = Texture->PlatformData->Mips[0 ]; uint8* TextureData = (uint8*) Mip.BulkData.Lock (LOCK_READ_WRITE); Mip.BulkData.Unlock (); int32 SizeX = Texture->PlatformData->SizeX; int32 SizeY = Texture->PlatformData->SizeY; TArray<FColor> ColorData; for (int32 IndexY = 0 ; IndexY < SizeY; IndexY++) { for (int32 IndexX = 0 ; IndexX < SizeX; IndexX++) { FColor PixelColor; PixelColor.B = TextureData[(IndexY * SizeX + IndexX) * 4 + 0 ]; PixelColor.G = TextureData[(IndexY * SizeX + IndexX) * 4 + 1 ]; PixelColor.R = TextureData[(IndexY * SizeX + IndexX) * 4 + 2 ]; PixelColor.A = TextureData[(IndexY * SizeX + IndexX) * 4 + 3 ]; ColorData.Add (PixelColor); } } TArray<uint8> ImageData; FImageUtils::CompressImageArray (SizeX, SizeY, ColorData, ImageData); FFileHelper::SaveArrayToFile (ImageData, *PicturePath); Texture->CompressionSettings = OldCompressionSettings; Texture->MipGenSettings = OldMipGenSettings; Texture->SRGB = OldSRGB; Texture->UpdateResource (); }
参考源码
GameViewportClient : UGameViewportClient::ProcessScreenShots()
:实现了导出屏幕截图
Sprite -> PNG
分析
首先我们来看一下 Sprite 的信息:
其中,SourceUV 切割出的图片,左上角的偏移量;Source Dimension 表示切割图片的大小;SourceTexture 表示从哪个 Texture 切割而来。
举个例子:
在这幅图中,Texture->PlatformData->SizeX = 9
、Texture->PlatformData->SizeY = 5
,
假设我们切割的是红色部分,则 SourceUV = (3, 1)
、SourceDimension = (4, 3)
,此时的偏移量应该是 9(第一行) + 3(第二行空白处) = 12
我们发现 PaperSprite 中预留了这个函数,可以让我们获取到这三个信息:
这下子就很明确了,我们拿到这些信息,通过计算偏移量,就可以从 SourceTexture 中切割出图片来,剩下的内容和 Texture -> PNG 就一样了。
还一些需要注意的是,记得包含 PaperSpriteComponent.h
、PaperSprite.h
,如果在插件中使用 Sprite,需要依赖模块 Paper2D
。
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 void WriteSprite (UPaperSprite* Sprite) { FString PicturePath = ExportImageDirectory.Path + TEXT ("/" ) + Sprite->GetName () + TEXT (".png" ); UTexture2D* Texture = Sprite->GetSourceTexture (); TextureCompressionSettings OldCompressionSettings = Texture->CompressionSettings; TextureMipGenSettings OldMipGenSettings = Texture->MipGenSettings; bool OldSRGB = Texture->SRGB; Texture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap; Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps; Texture->SRGB = false ; Texture->UpdateResource (); FTexture2DMipMap& Mip = Texture->PlatformData->Mips[0 ]; uint8* TextureData = (uint8*) Mip.BulkData.Lock (LOCK_READ_WRITE); Mip.BulkData.Unlock (); FVector2D SourceUV = Sprite->GetSourceUV (); FVector2D SourceSize = Sprite->GetSourceSize (); int32 SizeX = (int32)SourceSize.X; int32 SizeY = (int32)SourceSize.Y; int32 Offsets = (int32)SourceUV.Y * Texture->PlatformData->SizeX + (int32)SourceUV.X; TArray<FColor> ColorData; for (int32 IndexY = 0 ; IndexY < SizeY; IndexY++) { for (int32 IndexX = 0 ; IndexX < SizeX; IndexX++) { FColor PixelColor; PixelColor.B = TextureData[(IndexY * Texture->PlatformData->SizeX + IndexX + Offsets) * 4 + 0 ]; PixelColor.G = TextureData[(IndexY * Texture->PlatformData->SizeX + IndexX + Offsets) * 4 + 1 ]; PixelColor.R = TextureData[(IndexY * Texture->PlatformData->SizeX + IndexX + Offsets) * 4 + 2 ]; PixelColor.A = TextureData[(IndexY * Texture->PlatformData->SizeX + IndexX + Offsets) * 4 + 3 ]; ColorData.Add (PixelColor); } } TArray<uint8> ImageData; FImageUtils::CompressImageArray (SizeX, SizeY, ColorData, ImageData); FFileHelper::SaveArrayToFile (ImageData, *PicturePath); Texture->CompressionSettings = OldCompressionSettings; Texture->MipGenSettings = OldMipGenSettings; Texture->SRGB = OldSRGB; Texture->UpdateResource (); }