void FTextureSource::Compress() { if (CanPNGCompress()) { uint8* BulkDataPtr = (uint8*)BulkData.Lock(LOCK_READ_WRITE); IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>( FName("ImageWrapper") ); IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper( EImageFormat::PNG ); // TODO: TSF_BGRA8 is stored as RGBA, so the R and B channels are swapped in the internal png. Should we fix this? ERGBFormat::Type RawFormat = (Format == TSF_G8) ? ERGBFormat::Gray : ERGBFormat::RGBA; if ( ImageWrapper.IsValid() && ImageWrapper->SetRaw( BulkDataPtr, BulkData.GetBulkDataSize(), SizeX, SizeY, RawFormat, Format == TSF_RGBA16 ? 16 : 8 ) ) { const TArray<uint8>& CompressedData = ImageWrapper->GetCompressed(); if ( CompressedData.Num() > 0 ) { BulkDataPtr = (uint8*)BulkData.Realloc(CompressedData.Num()); FMemory::Memcpy(BulkDataPtr, CompressedData.GetTypedData(), CompressedData.Num()); BulkData.Unlock(); bPNGCompressed = true; BulkData.StoreCompressedOnDisk(ECompressionFlags::COMPRESS_None); } } } else { //Can't PNG compress so just zlib compress the lot when its serialized out to disk. BulkData.StoreCompressedOnDisk(ECompressionFlags::COMPRESS_ZLIB); } }
void FSSTBatchCombinerModule::PluginButtonClicked() { TArray<FString> OutFileNames; TArray<FString> OutFileNames2; FDesktopPlatformModule::Get()->OpenFileDialog(nullptr, "select First Image Files", "", "", "Image Files (*.png)|*.png", 1, OutFileNames); FDesktopPlatformModule::Get()->OpenFileDialog(nullptr, "select Second Image Files", "", "", "Image Files (*.png)|*.png", 1, OutFileNames2); if (OutFileNames.Num() && OutFileNames2.Num()) { if (OutFileNames.Num() != OutFileNames2.Num()) { FString DialogText = "Error! first set quantity does not match second set!"; FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(DialogText)); return; } IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper")); for (int32 i = 0; i < OutFileNames.Num(); i++) { TArray<uint8> RawFileData; TArray<uint8> RawFileData2; if (FFileHelper::LoadFileToArray(RawFileData, *OutFileNames[i]) && FFileHelper::LoadFileToArray(RawFileData2, *OutFileNames2[i])) { IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); IImageWrapperPtr ImageWrapper2 = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num()) && ImageWrapper2.IsValid() && ImageWrapper2->SetCompressed(RawFileData2.GetData(), RawFileData2.Num())) { const TArray<uint8>* RawData = nullptr; const TArray<uint8>* RawData2 = nullptr; if (ImageWrapper->GetRaw(ERGBFormat::BGRA, ImageWrapper->GetBitDepth(), RawData) && ImageWrapper2->GetRaw(ERGBFormat::BGRA, ImageWrapper2->GetBitDepth(), RawData2)) { uint32 ImageWidth = ImageWrapper->GetWidth(); uint32 ImageHeight = ImageWrapper->GetHeight(); uint32 ImageWidth2 = ImageWrapper2->GetWidth(); uint32 ImageHeight2 = ImageWrapper2->GetHeight(); if ((ImageWidth != ImageWidth2) || (ImageHeight != ImageHeight2)) { FString DialogText = "Error! Image dimensions do not match for frame " + FString::FromInt(i); FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(DialogText)); return; } TArray<uint8> newdata; newdata = *RawData; for (int32 a = 0; a < RawData->Num() - 4; a = a + 4) { newdata[a + 1] = (*RawData2)[a]; } //save ImageWrapper->SetRaw(newdata.GetData(), newdata.GetAllocatedSize(), ImageWidth, ImageHeight, ERGBFormat::BGRA, 8); const TArray<uint8>& PNGData = ImageWrapper->GetCompressed(100); FFileHelper::SaveArrayToFile(PNGData, *OutFileNames[i]); } //if imagewrapper.getraw } //if imagewrapper.valid } //if load file }// for files loop } //if files }
static bool CompressSliceToASTC( const void* SourceData, int32 SizeX, int32 SizeY, FString CompressionParameters, TArray<uint8>& OutCompressedData ) { // Always Y-invert the image prior to compression for proper orientation post-compression uint8 LineBuffer[16384 * 4]; uint32 LineSize = SizeX * 4; for (int32 LineIndex = 0; LineIndex < (SizeY / 2); LineIndex++) { uint8* LineData0 = ((uint8*)SourceData) + (LineSize * LineIndex); uint8* LineData1 = ((uint8*)SourceData) + (LineSize * (SizeY - LineIndex - 1)); FMemory::Memcpy(LineBuffer, LineData0, LineSize); FMemory::Memcpy(LineData0, LineData1, LineSize); FMemory::Memcpy(LineData1, LineBuffer, LineSize); } // Compress and retrieve the PNG data to write out to disk IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); ImageWrapper->SetRaw(SourceData, SizeX * SizeY * 4, SizeX, SizeY, ERGBFormat::RGBA, 8); const TArray<uint8>& FileData = ImageWrapper->GetCompressed(); int32 FileDataSize = FileData.Num(); FGuid Guid; FPlatformMisc::CreateGuid(Guid); FString InputFilePath = FString::Printf(TEXT("Cache/%08x-%08x-%08x-%08x-RGBToASTCIn.png"), Guid.A, Guid.B, Guid.C, Guid.D); FString OutputFilePath = FString::Printf(TEXT("Cache/%08x-%08x-%08x-%08x-RGBToASTCOut.astc"), Guid.A, Guid.B, Guid.C, Guid.D); InputFilePath = FPaths::GameIntermediateDir() + InputFilePath; OutputFilePath = FPaths::GameIntermediateDir() + OutputFilePath; FArchive* PNGFile = NULL; while (!PNGFile) { PNGFile = IFileManager::Get().CreateFileWriter(*InputFilePath); // Occasionally returns NULL due to error code ERROR_SHARING_VIOLATION FPlatformProcess::Sleep(0.01f); // ... no choice but to wait for the file to become free to access } PNGFile->Serialize((void*)&FileData[0], FileDataSize); delete PNGFile; // Compress PNG file to ASTC (using the reference astcenc.exe from ARM) FString Params = FString::Printf(TEXT("-c %s %s %s"), *InputFilePath, *OutputFilePath, *CompressionParameters ); UE_LOG(LogTextureFormatASTC, Display, TEXT("Compressing to ASTC (%s)..."), *CompressionParameters); // Start Compressor #if PLATFORM_MAC FString CompressorPath(FPaths::EngineDir() + TEXT("Binaries/ThirdParty/ARM/Mac/astcenc")); #elif PLATFORM_LINUX FString CompressorPath(FPaths::EngineDir() + TEXT("Binaries/ThirdParty/ARM/Linux32/astcenc")); #elif PLATFORM_WINDOWS FString CompressorPath(FPaths::EngineDir() + TEXT("Binaries/ThirdParty/ARM/Win32/astcenc.exe")); #else #error Unsupported platform #endif FProcHandle Proc = FPlatformProcess::CreateProc(*CompressorPath, *Params, true, false, false, NULL, -1, NULL, NULL); // Failed to start the compressor process if (!Proc.IsValid()) { UE_LOG(LogTextureFormatASTC, Error, TEXT("Failed to start astcenc for compressing images (%s)"), *CompressorPath); return false; } // Wait for the process to complete int ReturnCode; while (!FPlatformProcess::GetProcReturnCode(Proc, &ReturnCode)) { FPlatformProcess::Sleep(0.01f); } // Did it work? bool bConversionWasSuccessful = (ReturnCode == 0); // Open compressed file and put the data in OutCompressedImage if (bConversionWasSuccessful) { // Get raw file data TArray<uint8> ASTCData; FFileHelper::LoadFileToArray(ASTCData, *OutputFilePath); // Process it FASTCHeader* Header = (FASTCHeader*)ASTCData.GetData(); // Fiddle with the texel count data to get the right value uint32 TexelCountX = (Header->TexelCountX[0] << 0) + (Header->TexelCountX[1] << 8) + (Header->TexelCountX[2] << 16); uint32 TexelCountY = (Header->TexelCountY[0] << 0) + (Header->TexelCountY[1] << 8) + (Header->TexelCountY[2] << 16); uint32 TexelCountZ = (Header->TexelCountZ[0] << 0) + (Header->TexelCountZ[1] << 8) + (Header->TexelCountZ[2] << 16); // UE_LOG(LogTextureFormatASTC, Display, TEXT(" Compressed Texture Header:")); // UE_LOG(LogTextureFormatASTC, Display, TEXT(" Magic: %x"), Header->Magic); // UE_LOG(LogTextureFormatASTC, Display, TEXT(" BlockSizeX: %u"), Header->BlockSizeX); // UE_LOG(LogTextureFormatASTC, Display, TEXT(" BlockSizeY: %u"), Header->BlockSizeY); // UE_LOG(LogTextureFormatASTC, Display, TEXT(" BlockSizeZ: %u"), Header->BlockSizeZ); // UE_LOG(LogTextureFormatASTC, Display, TEXT(" TexelCountX: %u"), TexelCountX); // UE_LOG(LogTextureFormatASTC, Display, TEXT(" TexelCountY: %u"), TexelCountY); // UE_LOG(LogTextureFormatASTC, Display, TEXT(" TexelCountZ: %u"), TexelCountZ); // Calculate size of this mip in blocks uint32 MipSizeX = (TexelCountX + Header->BlockSizeX - 1) / Header->BlockSizeX; uint32 MipSizeY = (TexelCountY + Header->BlockSizeY - 1) / Header->BlockSizeY; // A block is always 16 bytes uint32 MipSize = MipSizeX * MipSizeY * 16; // Copy the compressed data OutCompressedData.Empty(MipSize); OutCompressedData.AddUninitialized(MipSize); void* MipData = OutCompressedData.GetData(); // Calculate the offset to get to the mip data check(sizeof(FASTCHeader) == 16); check(ASTCData.Num() == (sizeof(FASTCHeader) + MipSize)); FMemory::Memcpy(MipData, ASTCData.GetData() + sizeof(FASTCHeader), MipSize); } else { UE_LOG(LogTextureFormatASTC, Error, TEXT("ASTC encoder failed with return code %d, mip size (%d, %d)"), ReturnCode, SizeX, SizeY); IFileManager::Get().Delete(*InputFilePath); IFileManager::Get().Delete(*OutputFilePath); return false; } // Delete intermediate files IFileManager::Get().Delete(*InputFilePath); IFileManager::Get().Delete(*OutputFilePath); return true; }
void USceneCapturer::CaptureComponent( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, FString Folder, USceneCaptureComponent2D* CaptureComponent, TArray<FColor>& Atlas ) { TArray<FColor> SurfaceData; { SCOPE_CYCLE_COUNTER( STAT_SPReadStrip ); FTextureRenderTargetResource* RenderTarget = CaptureComponent->TextureTarget->GameThread_GetRenderTargetResource(); //TODO: ikrimae: Might need to validate that this divides evenly. Might not matter int32 CenterX = CaptureWidth / 2; int32 CenterY = CaptureHeight / 2; SurfaceData.AddUninitialized( StripWidth * StripHeight ); // Read pixels FIntRect Area( CenterX - ( StripWidth / 2 ), CenterY - ( StripHeight / 2 ), CenterX + ( StripWidth / 2 ), CenterY + ( StripHeight / 2) ); auto readSurfaceDataFlags = FReadSurfaceDataFlags(); readSurfaceDataFlags.SetLinearToGamma(false); RenderTarget->ReadPixelsPtr( SurfaceData.GetData(), readSurfaceDataFlags, Area ); } // Copy off strip to atlas texture CopyToUnprojAtlas( CurrentHorizontalStep, CurrentVerticalStep, Atlas, SurfaceData ); if( FStereoPanoramaManager::GenerateDebugImages->GetInt() != 0 ) { SCOPE_CYCLE_COUNTER( STAT_SPSavePNG ); // Generate name FString TickString = FString::Printf( TEXT( "_%05d_%04d_%04d" ), CurrentFrameCount, CurrentHorizontalStep, CurrentVerticalStep ); FString CaptureName = OutputDir / Timestamp / Folder / TickString + TEXT( ".png" ); UE_LOG( LogStereoPanorama, Log, TEXT( "Writing snapshot: %s" ), *CaptureName ); // Write out PNG if (FStereoPanoramaManager::GenerateDebugImages->GetInt() == 2) { //Read Whole Capture Buffer IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper( EImageFormat::PNG ); TArray<FColor> SurfaceDataWhole; SurfaceDataWhole.AddUninitialized(CaptureWidth * CaptureHeight); // Read pixels FTextureRenderTargetResource* RenderTarget = CaptureComponent->TextureTarget->GameThread_GetRenderTargetResource(); RenderTarget->ReadPixelsPtr(SurfaceDataWhole.GetData(), FReadSurfaceDataFlags()); // Force alpha value if (bForceAlpha) { for (FColor& Color : SurfaceDataWhole) { Color.A = 255; } } ImageWrapper->SetRaw(SurfaceDataWhole.GetData(), SurfaceDataWhole.GetAllocatedSize(), CaptureWidth, CaptureHeight, ERGBFormat::BGRA, 8); const TArray<uint8>& PNGData = ImageWrapper->GetCompressed(100); FFileHelper::SaveArrayToFile(PNGData, *CaptureName); ImageWrapper.Reset(); } else { if (bForceAlpha) { for (FColor& Color : SurfaceData) { Color.A = 255; } } IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); ImageWrapper->SetRaw(SurfaceData.GetData(), SurfaceData.GetAllocatedSize(), StripWidth, StripHeight, ERGBFormat::BGRA, 8); const TArray<uint8>& PNGData = ImageWrapper->GetCompressed(100); FFileHelper::SaveArrayToFile( PNGData, *CaptureName ); ImageWrapper.Reset(); } } }
TArray<FColor> USceneCapturer::SaveAtlas(FString Folder, const TArray<FColor>& SurfaceData) { SCOPE_CYCLE_COUNTER( STAT_SPSavePNG ); TArray<FColor> SphericalAtlas; SphericalAtlas.AddZeroed(SphericalAtlasWidth * SphericalAtlasHeight); const FVector2D slicePlaneDim = FVector2D( 2.0f * FMath::Tan(FMath::DegreesToRadians(sliceHFov) / 2.0f), 2.0f * FMath::Tan(FMath::DegreesToRadians(sliceVFov) / 2.0f)); //For each direction, // Find corresponding slice // Calculate intersection of slice plane // Calculate intersection UVs by projecting onto plane tangents // Supersample that UV coordinate from the unprojected atlas { SCOPE_CYCLE_COUNTER(STAT_SPSampleSpherical); // Dump out how long the process took const FDateTime SamplingStartTime = FDateTime::UtcNow(); UE_LOG(LogStereoPanorama, Log, TEXT("Sampling atlas...")); for (int32 y = 0; y < SphericalAtlasHeight; y++) { for (int32 x = 0; x < SphericalAtlasWidth; x++) { FLinearColor samplePixelAccum = FLinearColor(0, 0, 0, 0); //TODO: ikrimae: Seems that bilinear filtering sans supersampling is good enough. Supersampling sans bilerp seems best. // After more tests, come back to optimize by folding supersampling in and remove this outer sampling loop. const auto& ssPattern = g_ssPatterns[SSMethod]; for (int32 SampleCount = 0; SampleCount < ssPattern.numSamples; SampleCount++) { const float sampleU = ((float)x + ssPattern.ssOffsets[SampleCount].X) / SphericalAtlasWidth; const float sampleV = ((float)y + ssPattern.ssOffsets[SampleCount].Y) / SphericalAtlasHeight; const float sampleTheta = sampleU * 360.0f; const float samplePhi = sampleV * 180.0f; const FVector sampleDir = FVector( FMath::Sin(FMath::DegreesToRadians(samplePhi)) * FMath::Cos(FMath::DegreesToRadians(sampleTheta)), FMath::Sin(FMath::DegreesToRadians(samplePhi)) * FMath::Sin(FMath::DegreesToRadians(sampleTheta)), FMath::Cos(FMath::DegreesToRadians(samplePhi))); //TODO: ikrimae: ugh, ugly. const int32 sliceXIndex = FMath::TruncToInt(FRotator::ClampAxis(sampleTheta + hAngIncrement / 2.0f) / hAngIncrement); int32 sliceYIndex = 0; //Slice Selection = slice with max{sampleDir dot sliceNormal } { float largestCosAngle = 0; for (int VerticalStep = 0; VerticalStep < NumberOfVerticalSteps; VerticalStep++) { const FVector2D sliceCenterThetaPhi = FVector2D( hAngIncrement * sliceXIndex, vAngIncrement * VerticalStep); //TODO: ikrimae: There has got to be a faster way. Rethink reparametrization later const FVector sliceDir = FVector( FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y))); const float cosAngle = sampleDir | sliceDir; if (cosAngle > largestCosAngle) { largestCosAngle = cosAngle; sliceYIndex = VerticalStep; } } } const FVector2D sliceCenterThetaPhi = FVector2D( hAngIncrement * sliceXIndex, vAngIncrement * sliceYIndex); //TODO: ikrimae: Reparameterize with an inverse mapping (e.g. project from slice pixels onto final u,v coordinates. // Should make code simpler and faster b/c reduces to handful of sin/cos calcs per slice. // Supersampling will be more difficult though. const FVector sliceDir = FVector( FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y))); const FPlane slicePlane = FPlane(sliceDir, -sliceDir); //Tangents from partial derivatives of sphere equation const FVector slicePlanePhiTangent = FVector( FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), -FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y))).GetSafeNormal(); //Should be reconstructed to get around discontinuity of theta tangent at nodal points const FVector slicePlaneThetaTangent = (sliceDir ^ slicePlanePhiTangent).GetSafeNormal(); //const FVector slicePlaneThetaTangent = FVector( // -FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), // FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), // 0).SafeNormal(); check(!slicePlaneThetaTangent.IsZero() && !slicePlanePhiTangent.IsZero()); const double t = (double)-slicePlane.W / (sampleDir | sliceDir); const FVector sliceIntersection = FVector(t * sampleDir.X, t * sampleDir.Y, t * sampleDir.Z); //Calculate scalar projection of sliceIntersection onto tangent vectors. a dot b / |b| = a dot b when tangent vectors are normalized //Then reparameterize to U,V of the sliceplane based on slice plane dimensions const float sliceU = (sliceIntersection | slicePlaneThetaTangent) / slicePlaneDim.X; const float sliceV = (sliceIntersection | slicePlanePhiTangent) / slicePlaneDim.Y; check(sliceU >= -(0.5f + KINDA_SMALL_NUMBER) && sliceU <= (0.5f + KINDA_SMALL_NUMBER)); check(sliceV >= -(0.5f + KINDA_SMALL_NUMBER) && sliceV <= (0.5f + KINDA_SMALL_NUMBER)); //TODO: ikrimae: Supersample/bilinear filter const int32 slicePixelX = FMath::TruncToInt(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceU * StripWidth : sliceU * CaptureWidth); const int32 slicePixelY = FMath::TruncToInt(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceV * StripHeight : sliceV * CaptureHeight); FLinearColor slicePixelSample; if (bEnableBilerp) { //TODO: ikrimae: Clean up later; too tired now const int32 sliceCenterPixelX = (sliceXIndex + 0.5f) * StripWidth; const int32 sliceCenterPixelY = (sliceYIndex + 0.5f) * StripHeight; const FIntPoint atlasSampleTL(sliceCenterPixelX + FMath::Clamp(slicePixelX , -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY , -StripHeight/2, StripHeight/2)); const FIntPoint atlasSampleTR(sliceCenterPixelX + FMath::Clamp(slicePixelX + 1, -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY , -StripHeight/2, StripHeight/2)); const FIntPoint atlasSampleBL(sliceCenterPixelX + FMath::Clamp(slicePixelX , -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY + 1, -StripHeight/2, StripHeight/2)); const FIntPoint atlasSampleBR(sliceCenterPixelX + FMath::Clamp(slicePixelX + 1, -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY + 1, -StripHeight/2, StripHeight/2)); const FColor pixelColorTL = SurfaceData[atlasSampleTL.Y * UnprojectedAtlasWidth + atlasSampleTL.X]; const FColor pixelColorTR = SurfaceData[atlasSampleTR.Y * UnprojectedAtlasWidth + atlasSampleTR.X]; const FColor pixelColorBL = SurfaceData[atlasSampleBL.Y * UnprojectedAtlasWidth + atlasSampleBL.X]; const FColor pixelColorBR = SurfaceData[atlasSampleBR.Y * UnprojectedAtlasWidth + atlasSampleBR.X]; const float fracX = FMath::Frac(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceU * StripWidth : sliceU * CaptureWidth); const float fracY = FMath::Frac(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceV * StripHeight : sliceV * CaptureHeight); //Reinterpret as linear (a.k.a dont apply srgb inversion) slicePixelSample = FMath::BiLerp( pixelColorTL.ReinterpretAsLinear(), pixelColorTR.ReinterpretAsLinear(), pixelColorBL.ReinterpretAsLinear(), pixelColorBR.ReinterpretAsLinear(), fracX, fracY); } else { const int32 sliceCenterPixelX = (sliceXIndex + 0.5f) * StripWidth; const int32 sliceCenterPixelY = (sliceYIndex + 0.5f) * StripHeight; const int32 atlasSampleX = sliceCenterPixelX + slicePixelX; const int32 atlasSampleY = sliceCenterPixelY + slicePixelY; slicePixelSample = SurfaceData[atlasSampleY * UnprojectedAtlasWidth + atlasSampleX].ReinterpretAsLinear(); } samplePixelAccum += slicePixelSample; ////Output color map of projections //const FColor debugEquiColors[12] = { // FColor(205, 180, 76), // FColor(190, 88, 202), // FColor(127, 185, 194), // FColor(90, 54, 47), // FColor(197, 88, 53), // FColor(197, 75, 124), // FColor(130, 208, 72), // FColor(136, 211, 153), // FColor(126, 130, 207), // FColor(83, 107, 59), // FColor(200, 160, 157), // FColor(80, 66, 106) //}; //samplePixelAccum = ssPattern.numSamples * debugEquiColors[sliceYIndex * 4 + sliceXIndex]; } SphericalAtlas[y * SphericalAtlasWidth + x] = (samplePixelAccum / ssPattern.numSamples).Quantize(); // Force alpha value if (bForceAlpha) { SphericalAtlas[y * SphericalAtlasWidth + x].A = 255; } } } //Blit the first column into the last column to make the stereo image seamless at theta=360 for (int32 y = 0; y < SphericalAtlasHeight; y++) { SphericalAtlas[y * SphericalAtlasWidth + (SphericalAtlasWidth - 1)] = SphericalAtlas[y * SphericalAtlasWidth + 0]; } const FTimespan SamplingDuration = FDateTime::UtcNow() - SamplingStartTime; UE_LOG(LogStereoPanorama, Log, TEXT("...done! Duration: %g seconds"), SamplingDuration.GetTotalSeconds()); } // Generate name FString FrameString = FString::Printf( TEXT( "%s_%05d.png" ), *Folder, CurrentFrameCount ); FString AtlasName = OutputDir / Timestamp / FrameString; UE_LOG( LogStereoPanorama, Log, TEXT( "Writing atlas: %s" ), *AtlasName ); // Write out PNG //TODO: ikrimae: Use threads to write out the images for performance IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper( EImageFormat::PNG ); ImageWrapper->SetRaw(SphericalAtlas.GetData(), SphericalAtlas.GetAllocatedSize(), SphericalAtlasWidth, SphericalAtlasHeight, ERGBFormat::BGRA, 8); const TArray<uint8>& PNGData = ImageWrapper->GetCompressed(100); FFileHelper::SaveArrayToFile( PNGData, *AtlasName ); if (FStereoPanoramaManager::GenerateDebugImages->GetInt() != 0) { FString FrameStringUnprojected = FString::Printf(TEXT("%s_%05d_Unprojected.png"), *Folder, CurrentFrameCount); FString AtlasNameUnprojected = OutputDir / Timestamp / FrameStringUnprojected; ImageWrapper->SetRaw(SurfaceData.GetData(), SurfaceData.GetAllocatedSize(), UnprojectedAtlasWidth, UnprojectedAtlasHeight, ERGBFormat::BGRA, 8); const TArray<uint8>& PNGDataUnprojected = ImageWrapper->GetCompressed(100); FFileHelper::SaveArrayToFile(PNGData, *AtlasNameUnprojected); } ImageWrapper.Reset(); UE_LOG( LogStereoPanorama, Log, TEXT( " ... done!" ), *AtlasName ); return SphericalAtlas; }
void FCrashVideoCapture::Update(float DeltaSeconds) { SCOPE_CYCLE_COUNTER(STAT_TotalTime); if (bIsRunning) { // Update the key press buffer times for (int32 i = 0; i < KeypressBuffer.Num(); ++i) { KeypressBuffer[i].Value -= DeltaSeconds; } // check to see if we need to render a new frame bool bShouldUpdate = false; CurrentAccumSeconds += DeltaSeconds; if (CurrentAccumSeconds > CrashTrackerConstants::CaptureFrequency) { CurrentAccumSeconds -= FMath::TruncToFloat(CurrentAccumSeconds / CrashTrackerConstants::CaptureFrequency) * CrashTrackerConstants::CaptureFrequency; bShouldUpdate = true; } if (bShouldUpdate) { CleanupKeyPressBuffer(DeltaSeconds); TArray<FString> StrippedKeyPressBuffer; for (int32 i = 0; i < KeypressBuffer.Num(); ++i) { StrippedKeyPressBuffer.Add(KeypressBuffer[i].Key); } const FMappedTextureBuffer& CurrentBuffer = Buffer[CurrentBufferIndex]; if ( CurrentBuffer.IsValid() ) { IImageWrapperPtr ImageWrapper; { SCOPE_CYCLE_COUNTER(STAT_SendFrameToCompressor); IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>( FName("ImageWrapper") ); ImageWrapper = ImageWrapperModule.CreateImageWrapper( EImageFormat::JPEG ); ImageWrapper->SetRaw(CurrentBuffer.Data, CurrentBuffer.Width * CurrentBuffer.Height * sizeof(FColor), CurrentBuffer.Width, CurrentBuffer.Height, ERGBFormat::RGBA, 8); } { SCOPE_CYCLE_COUNTER(STAT_WaitForPreviousFrameToCompress); SyncWorkerThread(); AsyncTask = new FAsyncTask<class FAsyncImageCompress>(ImageWrapper, CompressedFrames[CurrentFrameCaptureIndex]); AsyncTask->StartBackgroundTask(); } CaptureSlateRenderer->UnmapVirtualScreenBuffer(); } ++CurrentFrameCaptureIndex; if (CurrentFrameCaptureIndex >= CrashTrackerConstants::VideoFramesToCapture) { CurrentFrameCaptureIndex = 0; } const bool bPrimaryWorkAreaOnly = false; // We want all monitors captured for crash reporting const FIntRect VirtualScreen = CaptureSlateRenderer->SetupVirtualScreenBuffer(bPrimaryWorkAreaOnly, CrashTrackerConstants::ScreenScaling, nullptr); Width = VirtualScreen.Width(); Height = VirtualScreen.Height(); CaptureSlateRenderer->CopyWindowsToVirtualScreenBuffer(StrippedKeyPressBuffer); CaptureSlateRenderer->MapVirtualScreenBuffer(&Buffer[CurrentBufferIndex]); CurrentBufferIndex = (CurrentBufferIndex + 1) % 2; } } }