Download

Texture to PNG fails on device

I’m trying to save a given UTexture2D as png. Current code works fine on Windows and in editor.
But when compiled for device, the for loop crashes the app. In some cases it writes a garbled png file.

I think this has something to do with using ASTC as the texture format. While reading the raw data I think the compressed code is being sent to me and not the raw data.
So, the for loop crash or wrong colors from the byte array.

What is the correct way of getting uncompressed bulk data on android for ASTC formatted textures in UE4?

Here is the code I’m currently using:


void UHitMeSingleton::Texture2DToPNG(UTexture2D* Texture, FString FileName, bool& IsValid) {
	IsValid = false;

	{
		bool isOK = UCaller::CreateDataFolder();
		if (!isOK) return;
	}

	if (!Texture) return;

#if WITH_EDITOR
	Texture->MipGenSettings = TMGS_NoMipmaps;
#endif
	Texture->SRGB = false;
	Texture->CompressionSettings = TC_VectorDisplacementmap;
	Texture->UpdateResource();

	FTexture2DMipMap& CurrentMipMap = Texture->PlatformData->Mips[0];
	int32 TextureWidth = CurrentMipMap.SizeX;
	int32 TextureHeight = CurrentMipMap.SizeY;
	if (TextureWidth <= 0 || TextureHeight <= 0) return;

	FColor* RawColorArray = static_cast<FColor*>(Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_ONLY));
	if (!RawColorArray) return;

	TArray<FColor> PixelArray;
	FColor PixelColor;
	for (int32 y = 0; y < TextureHeight; y++) {
		for (int32 x = 0; x < TextureWidth; x++) {
			PixelColor = RawColorArray[y * TextureWidth + x];
			PixelArray.Add(PixelColor);
		}
	}

	Texture->PlatformData->Mips[0].BulkData.Unlock();
	Texture->UpdateResource();

	{
		IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
		IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);

		bool rawOk = ImageWrapper->SetRaw(RawColorArray, PixelArray.Num() * sizeof(FColor), TextureWidth, TextureHeight, ERGBFormat::BGRA, 8);
		if (rawOk) {
			FString FullFilePath = UCaller::LocalDataImageFolder() + "/" + FileName + ".png";
			IsValid = FFileHelper::SaveArrayToFile(ImageWrapper->GetCompressed(), *FullFilePath);
		}
	}
}

I haven’t use any of this code in the past but maybe you will find this post useful: https://answers.unrealengine.com/questions/25594/accessing-pixel-values-of-texture2d.html

Thanks but…
the last comment says that it is not working on Android :frowning:

Yeah i was looking at it too. Have you tried with ETC1 texture format, if it is possible for your project?

I’m cooking for ETC1 right now… will update the result here.

… nope, did not work with ETC1 either.

The texture data is compressed and decoded by the GPU. In the editor it has access to the uncompressed data; we do not on device.

Well, there it goes then :slight_smile:

Is there any way to read etc1 texture (that has alpha means it is not actually compressed)? Or any code in UE source that does something similar so I can copy?

Or another way I can work on, is it possible to add PNG files into PAKs, and access them when they get mounted?

so, i made some tests and it seems it is possible to package and access ordinary files.
I wrote a simple middleware to inject some files to the obb file (which is a zipped pak) generated for android.

So, after game starts I’m able to call a file (not an .asset file, but a .png, .json, whatever) and copy it to anywhere on device…

Here is the code if someone needs it.


void UCaller::WriteOut(FString ReadFile, FString WriteFile, bool& IsValid) {
	IsValid = false;

	IPlatformFile* LocalPlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
	if (!LocalPlatformFile) return;

	FString FromFile = FPaths::GameContentDir() / ReadFile;
	FString ToFile   = FPaths::GamePersistentDownloadDir() / WriteFile;

	UE_LOG(HITMELOG, Log, TEXT("[UCaller::WriteOut] Copying %s to %s"), *FromFile, *ToFile);

	if (LocalPlatformFile->DeleteFile(*ToFile)) {
		UE_LOG(HITMELOG, Log, TEXT("[UCaller::WriteOut] Deleted file %s"), *ToFile);
	} else {
		UE_LOG(HITMELOG, Warning, TEXT("[UCaller::WriteOut] Could not delete the file %s"), *ToFile);
	}

	IsValid = LocalPlatformFile->CopyFile(*ToFile, *FromFile, EPlatformFileRead::AllowWrite, EPlatformFileWrite::AllowRead);
}

CopyFile can only copy if a file with the same name does not exist at the location you are trying to write.
Be sure that you are deleting the file first, just in case.