WL
GIF MultiFrame Duration Parameter Missing
Printscreen_Formula_script.psc
22 lines
text
Printscreen_MainQuest_script.psc
466 lines
text
PapyrusInterface.cpp
285 lines
txt
plugin.cpp
53 lines
txt
ScreenCapture.cpp
483 lines
txt
WL
In reviewing the attached files I have determined that the paramiter GIF_MultiFrame_Duration a float paramater ranging from 1 to 30 seems to be missing from the TakePhoto function. This paramiter is necessary to determine how long the GIF_MultiFrame option captures frames. Please check that this parameter is properly passed to the capture function and the capture loop is correctly formulated to use it in capturing a multiFrame gif. Advise if any changes are required
Edit
Looking at your code, I can see that the GIF_MultiFrame_Duration parameter is indeed missing from the C++ implementation. The parameter is properly defined and passed in the Papyrus scripts, but it's not being handled correctly in the C++ code.
Here are the issues I found and the changes needed:
Issues Found:
- Missing parameter in C++ function signature - The
TakePhotofunction inPapyrusInterface.cppdoesn't include theGIF_MultiFrame_Durationparameter - Incorrect parameter mapping - The current code incorrectly uses
jpgCompressionas the GIF duration - Missing GIF capture implementation - The
ScreenCapture.cppdoesn't have the actual multi-frame GIF capture logic
Required Changes:
1. Update PapyrusInterface.cpp
Updated PapyrusInterface.cpp - TakePhoto Function
Code
// Updated TakePhoto function signature to include GIF_MultiFrame_Duration RE::BSFixedString TakePhoto( RE::StaticFunctionTag*, RE::BSFixedString basePath, RE::BSFixedString imageType, float jpgCompression, RE::BSFixedString compr
2. Update Function Registration
You'll also need to update the function registration in the RegisterFunctions method to include the new parameter:
Updated Function Registration
Code
// Updated function registration to match new signature bool RegisterFunctions(RE::BSScript::IVirtualMachine* vm) { if (!vm) { logger::error("Virtual machine is null"); return false; } // Register functions with the s
3. Update ScreenCapture.cpp for Multi-Frame GIF Support
The current ScreenCapture.cpp doesn't implement multi-frame GIF capture. Here's the missing implementation:
Multi-Frame GIF Implementation for ScreenCapture.cpp
Code
// Add this function to ScreenCapture.cpp for multi-frame GIF support CaptureResult CaptureMultiFrameGIF(const CaptureParams& params) { CaptureResult result; try { // Initialize COM HRESULT hr = CoInitializeEx(nullptr, CO
Summary of Required Changes:
- Update the
TakePhotofunction signature inPapyrusInterface.cppto include thegifMultiFrameDurationparameter - Update the
CaptureWorkerThreadfunction to accept and properly use the GIF duration parameter - Fix the parameter mapping so that
jpgCompressionis used for JPEG quality andgifMultiFrameDurationis used for GIF duration - Implement multi-frame GIF capture logic in
ScreenCapture.cpp - Update function registration to match the new signature
The key issue was that your Papyrus script was correctly passing 5 parameters, but your C++ function was only accepting 4 parameters, causing the GIF_MultiFrame_Duration to be ignored. The current implementation was incorrectly trying to use the JPEG compression value as the GIF duration.
These changes will ensure that:
- The GIF duration parameter is properly received from Papyrus
- Multi-frame GIFs are captured for the specified duration (1-30 seconds)
- The capture loop runs at an appropriate frame rate (10 FPS suggested)
- Each frame has the correct timing delay
- The cancellation mechanism works during the multi-frame capture process
Retry
WL
The modifications to screencapture.cpp are complex. Please provide me with a complete listing including all changes required as a n artifact
Edit
Complete ScreenCapture.cpp with Multi-Frame GIF Support
Code
#include "PCH.h" #include "ScreenCapture.h" #include "logger.h" namespace ScreenCapture { // Convert string to ImageFormat ImageFormat StringToImageFormat(const std::string& str) { std::string lower = str; std::transform
Retry
WL
Continue
Edit
Complete ScreenCapture.cpp with Multi-Frame GIF Support
Code ∙ Version 2
hr = frame->Commit(); if (FAILED(hr)) return hr; hr = encoder->Commit(); return hr; } }
The complete ScreenCapture.cpp file is now provided in the artifact above. Here's a summary of the key changes and additions:
Major Changes Made:
1. New Helper Functions
ConvertRGBAToBGRA()- Converts RGBA pixel data to BGRA format required by WICSetupDesktopDuplication()- Centralizes D3D11 and desktop duplication setupCaptureSingleFrame()- Handles capturing and processing a single frame
2. New Multi-Frame GIF Function
CaptureMultiFrameGIF()- Complete implementation for capturing animated GIFs- Captures frames at 10 FPS for the specified duration (1-30 seconds)
- Includes proper frame timing and delay settings
- Handles cancellation during the capture process
- Provides progress logging every 10 frames
3. Updated Main Function
CaptureScreen()now routes GIF_MULTIFRAME requests to the specialized function- Cleaner code structure with extracted helper functions
- Better error handling and resource management
4. Key Features of Multi-Frame GIF Implementation
Frame Rate & Timing:
- 10 FPS capture rate (adjustable by changing
frameRateconstant) - Proper frame delay calculation for smooth playback
- Duration validation (1-30 seconds as specified)
Cancellation Support:
- Checks
params.cancelFlagbetween each frame - Allows user to cancel long captures
Error Resilience:
- Continues capturing even if individual frames fail
- Reports successful frame count in result message
- Logs warnings for failed frames
Resource Management:
- Proper COM initialization/cleanup
- WIC encoder setup with GIF-specific properties
- DirectX resource management
5. Integration Points
The function integrates seamlessly with your existing Papyrus interface:
- Uses the same
CaptureParamsstructure - Returns
CaptureResultwith success status and messages - Respects the cancellation flag from the main thread
- Generates timestamped filenames matching your existing pattern
6. Performance Considerations
- Efficient frame capture using desktop duplication
- Minimal memory allocation per frame
- Sleep timing to maintain consistent frame rate
- Progress logging to monitor long captures
This implementation should handle the GIF_MultiFrame_Duration parameter correctly and provide smooth animated GIF capture functionality that integrates with your existing screenshot system.
Retry
Claude can make mistakes. Please double-check responses.
Research
beta
Sonnet 4
v2∙Latest
Copy
Publish
#include "PCH.h" #include "ScreenCapture.h" #include "logger.h" namespace ScreenCapture { // Convert string to ImageFormat ImageFormat StringToImageFormat(const std::string& str) { std::string lower = str; std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); if (lower == "png") return ImageFormat::PNG; if (lower == "jpg" || lower == "jpeg") return ImageFormat::JPEG; if (lower == "bmp") return ImageFormat::BMP; if (lower == "tif" || lower == "tiff") return ImageFormat::TIF; if (lower == "gif") return ImageFormat::GIF; if (lower == "gif_multiframe" || lower == "gif_multi") return ImageFormat::GIF_MULTIFRAME; if (lower == "dds") return ImageFormat::DDS; return ImageFormat::PNG; // Default } // Convert string to TiffCompression TiffCompression StringToTiffCompression(const std::string& str) { std::string lower = str; std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); if (lower == "none") return TiffCompression::None; if (lower == "rle") return TiffCompression::RLE; if (lower == "lzw") return TiffCompression::LZW; if (lower == "zip") return TiffCompression::ZIP; return TiffCompression::None; // Default } // Convert string to DDSCompression DDSCompression StringToDDSCompression(const std::string& str) { std::string lower = str; std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); if (lower == "bc1") return DDSCompression::BC1; if (lower == "bc2") return DDSCompression::BC2; if (lower == "bc3") return DDSCompression::BC3; if (lower == "bc4") return DDSCompression::BC4; if (lower == "bc5") return DDSCompression::BC5; if (lower == "bc6h") return DDSCompression::BC6H; if (lower == "bc7") return DDSCompression::BC7; if (lower == "rgba") return DDSCompression::RGBA; return DDSCompression::BC7; // Default } // Get WIC codec GUID GUID GetWICCodec(ImageFormat format) { switch (format) { case ImageFormat::PNG: return GUID_ContainerFormatPng; case ImageFormat::JPEG: return GUID_ContainerFormatJpeg; case ImageFormat::BMP: return GUID_ContainerFormatBmp; case ImageFormat::TIF: return GUID_ContainerFormatTiff; case ImageFormat::GIF: case ImageFormat::GIF_MULTIFRAME: return GUID_ContainerFormatGif; default: return GUID_ContainerFormatPng; } } // Get file extension std::wstring GetFileExtension(ImageFormat format) { switch (format) { case ImageFormat::PNG: return L".png"; case ImageFormat::JPEG: return L".jpg"; case ImageFormat::BMP: return L".bmp"; case ImageFormat::TIF: return L".tif"; case ImageFormat::GIF: case ImageFormat::GIF_MULTIFRAME: return L".gif"; case ImageFormat::DDS: return L".dds"; default: return L".png"; } } // Get DXGI format for DDS DXGI_FORMAT GetDXGIFormat(DDSCompression compression) { switch (compression) { case DDSCompression::BC1: return DXGI_FORMAT_BC1_UNORM; case DDSCompression::BC2: return DXGI_FORMAT_BC2_UNORM; case DDSCompression::BC3: return DXGI_FORMAT_BC3_UNORM; case DDSCompression::BC4: return DXGI_FORMAT_BC4_UNORM; case DDSCompression::BC5: return DXGI_FORMAT_BC5_UNORM; case DDSCompression::BC6H: return DXGI_FORMAT_BC6H_UF16; case DDSCompression::BC7: return DXGI_FORMAT_BC7_UNORM; case DDSCompression::RGBA: return DXGI_FORMAT_R8G8B8A8_UNORM; default: return DXGI_FORMAT_BC7_UNORM; } } // Helper function to convert RGBA to BGRA void ConvertRGBAToBGRA(const DirectX::Image* img, uint8_t* bgraData) { const uint8_t* src = img->pixels; for (size_t i = 0; i < img->width * img->height; ++i) { bgraData[i * 4 + 0] = src[i * 4 + 2]; // B bgraData[i * 4 + 1] = src[i * 4 + 1]; // G bgraData[i * 4 + 2] = src[i * 4 + 0]; // R bgraData[i * 4 + 3] = src[i * 4 + 3]; // A } } // Helper function to setup D3D11 and desktop duplication HRESULT SetupDesktopDuplication( Microsoft::WRL::ComPtr<ID3D11Device>& device, Microsoft::WRL::ComPtr<ID3D11DeviceContext>& context, Microsoft::WRL::ComPtr<IDXGIOutputDuplication>& duplication ) { // Create D3D11 device HRESULT hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &device, nullptr, &context ); if (FAILED(hr)) return hr; // Get DXGI device Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice; hr = device.As(&dxgiDevice); if (FAILED(hr)) return hr; // Get DXGI adapter Microsoft::WRL::ComPtr<IDXGIAdapter> adapter; hr = dxgiDevice->GetAdapter(&adapter); if (FAILED(hr)) return hr; // Get DXGI output (monitor) Microsoft::WRL::ComPtr<IDXGIOutput> output; hr = adapter->EnumOutputs(0, &output); if (FAILED(hr)) return hr; // Get DXGI output1 for desktop duplication Microsoft::WRL::ComPtr<IDXGIOutput1> output1; hr = output.As(&output1); if (FAILED(hr)) return hr; // Create desktop duplication hr = output1->DuplicateOutput(device.Get(), &duplication); return hr; } // Helper function to capture a single frame HRESULT CaptureSingleFrame( ID3D11Device* device, ID3D11DeviceContext* context, IDXGIOutputDuplication* duplication, DirectX::ScratchImage& frameImage ) { // Capture frame DXGI_OUTDUPL_FRAME_INFO frameInfo; Microsoft::WRL::ComPtr<IDXGIResource> resource; HRESULT hr = duplication->AcquireNextFrame(1000, &frameInfo, &resource); if (FAILED(hr)) return hr; // Get texture from resource Microsoft::WRL::ComPtr<ID3D11Texture2D> texture; hr = resource.As(&texture); if (FAILED(hr)) { duplication->ReleaseFrame(); return hr; } // Get texture description D3D11_TEXTURE2D_DESC desc; texture->GetDesc(&desc); // Create staging texture for CPU access desc.Usage = D3D11_USAGE_STAGING; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; desc.BindFlags = 0; desc.MiscFlags = 0; Microsoft::WRL::ComPtr<ID3D11Texture2D> stagingTexture; hr = device->CreateTexture2D(&desc, nullptr, &stagingTexture); if (FAILED(hr)) { duplication->ReleaseFrame(); return hr; } // Copy texture to staging context->CopyResource(stagingTexture.Get(), texture.Get()); // Map staging texture D3D11_MAPPED_SUBRESOURCE mapped; hr = context->Map(stagingTexture.Get(), 0, D3D11_MAP_READ, 0, &mapped); if (FAILED(hr)) { duplication->ReleaseFrame(); return hr; } // Create DirectXTex image DirectX::Image image; image.width = desc.Width; image.height = desc.Height; image.format = desc.Format; image.rowPitch = mapped.RowPitch; image.slicePitch = mapped.RowPitch * desc.Height; image.pixels = static_cast<uint8_t*>(mapped.pData); // Initialize ScratchImage hr = frameImage.InitializeFromImage(image); if (FAILED(hr)) { context->Unmap(stagingTexture.Get(), 0); duplication->ReleaseFrame(); return hr; } // Convert to RGBA if needed DirectX::ScratchImage convertedImage; if (desc.Format != DXGI_FORMAT_R8G8B8A8_UNORM) { hr = DirectX::Convert( frameImage.GetImages(), frameImage.GetImageCount(), frameImage.GetMetadata(), DXGI_FORMAT_R8G8B8A8_UNORM, DirectX::TEX_FILTER_DEFAULT, DirectX::TEX_THRESHOLD_DEFAULT, convertedImage ); if (SUCCEEDED(hr)) { frameImage = std::move(convertedImage); } } // Unmap and release frame context->Unmap(stagingTexture.Get(), 0); duplication->ReleaseFrame(); return hr; } // Multi-frame GIF capture function CaptureResult CaptureMultiFrameGIF(const CaptureParams& params) { CaptureResult result; try { logger::info("Starting multi-frame GIF capture for {} seconds", params.gifDuration); // Initialize COM HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); bool comInitialized = SUCCEEDED(hr); // Initialize WIC Microsoft::WRL::ComPtr<IWICImagingFactory> wicFactory; hr = CoCreateInstance( CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&wicFactory) ); if (FAILED(hr)) { result.message = "Failed to create WIC factory"; if (comInitialized) CoUninitialize(); return result; } // Create GIF encoder Microsoft::WRL::ComPtr<IWICBitmapEncoder> encoder; hr = wicFactory->CreateEncoder(GUID_ContainerFormatGif, nullptr, &encoder); if (FAILED(hr)) { result.message = "Failed to create GIF encoder"; if (comInitialized) CoUninitialize(); return result; } // Generate filename auto now = std::chrono::system_clock::now(); auto time_t_val = std::chrono::system_clock::to_time_t(now); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch()) % 1000; std::tm timeinfo{}; localtime_s(&timeinfo, &time_t_val); std::wstringstream filename; filename << params.basePath; filename << L"_" << std::put_time(&timeinfo, L"%Y%m%d_%H%M%S"); filename << L"_" << std::setfill(L'0') << std::setw(3) << ms.count(); filename << L".gif"; result.filepath = filename.str(); // Create file stream Microsoft::WRL::ComPtr<IWICStream> stream; hr = wicFactory->CreateStream(&stream); if (FAILED(hr)) { result.message = "Failed to create stream"; if (comInitialized) CoUninitialize(); return result; } hr = stream->InitializeFromFilename(result.filepath.c_str(), GENERIC_WRITE); if (FAILED(hr)) { result.message = "Failed to initialize file stream"; if (comInitialized) CoUninitialize(); return result; } hr = encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache); if (FAILED(hr)) { result.message = "Failed to initialize encoder"; if (comInitialized) CoUninitialize(); return result; } // Setup D3D11 for frame capture Microsoft::WRL::ComPtr<ID3D11Device> device; Microsoft::WRL::ComPtr<ID3D11DeviceContext> context; Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication; hr = SetupDesktopDuplication(device, context, duplication); if (FAILED(hr)) { result.message = "Failed to setup desktop duplication"; if (comInitialized) CoUninitialize(); return result; } // Calculate frame timing const float frameRate = 10.0f; // 10 FPS for GIF const float frameInterval = 1.0f / frameRate; const int totalFrames = static_cast<int>(params.gifDuration * frameRate); const int delayTime = static_cast<int>(frameInterval * 100); // Delay in centiseconds logger::info("Capturing {} frames over {} seconds at {} FPS", totalFrames, params.gifDuration, frameRate); int successfulFrames = 0; // Capture frames in loop for (int frameIndex = 0; frameIndex < totalFrames; ++frameIndex) { // Check for cancellation if (params.cancelFlag && params.cancelFlag->load()) { result.message = "Cancelled"; if (comInitialized) CoUninitialize(); return result; } // Capture single frame DirectX::ScratchImage frameImage; hr = CaptureSingleFrame(device.Get(), context.Get(), duplication.Get(), frameImage); if (FAILED(hr)) { logger::warn("Failed to capture frame {}, continuing...", frameIndex); // Small delay before retrying std::this_thread::sleep_for(std::chrono::milliseconds(10)); continue; } // Convert frame to GIF-compatible format Microsoft::WRL::ComPtr<IWICBitmapFrameEncode> frame; Microsoft::WRL::ComPtr<IPropertyBag2> props; hr = encoder->CreateNewFrame(&frame, &props); if (FAILED(hr)) { logger::warn("Failed to create frame {}", frameIndex); continue; } // Set frame delay PROPBAG2 delayOption = {}; delayOption.pstrName = const_cast<LPOLESTR>(L"FrameDelay"); VARIANT delayValue; VariantInit(&delayValue); delayValue.vt = VT_UI2; delayValue.uiVal = delayTime; hr = props->Write(1, &delayOption, &delayValue); VariantClear(&delayValue); if (FAILED(hr)) { logger::warn("Failed to set frame delay for frame {}", frameIndex); } hr = frame->Commit(); if (FAILED(hr)) return hr; hr = encoder->Commit(); return hr; } }Initialize(props.Get()); if (FAILED(hr)) { logger::warn("Failed to initialize frame {}", frameIndex); continue; } // Write frame data const DirectX::Image* img = frameImage.GetImage(0, 0, 0); if (img) { hr = frame->SetSize(static_cast<UINT>(img->width), static_cast<UINT>(img->height)); if (SUCCEEDED(hr)) { WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA; hr = frame->SetPixelFormat(&pixelFormat); if (SUCCEEDED(hr)) { // Convert RGBA to BGRA and write std::vector<uint8_t> bgraData(img->width * img->height * 4); ConvertRGBAToBGRA(img, bgraData.data()); hr = frame->WritePixels( static_cast<UINT>(img->height), static_cast<UINT>(img->width * 4), static_cast<UINT>(bgraData.size()), bgraData.data() ); if (SUCCEEDED(hr)) { hr = frame->Commit(); if (SUCCEEDED(hr)) { successfulFrames++; } } } } } if (FAILED(hr)) { logger::warn("Failed to write frame {} data", frameIndex); } // Wait for next frame (except for last frame) if (frameIndex < totalFrames - 1) { std::this_thread::sleep_for( std::chrono::milliseconds(static_cast<int>(frameInterval * 1000)) ); } if (frameIndex % 10 == 0) { // Log progress every 10 frames logger::debug("Captured frame {} of {} (successful: {})", frameIndex + 1, totalFrames, successfulFrames); } } // Commit encoder hr = encoder->Commit(); if (FAILED(hr)) { result.message = "Failed to commit GIF encoder"; if (comInitialized) CoUninitialize(); return result; } if (successfulFrames > 0) { result.success = true; result.message = "Multi-frame GIF captured successfully (" + std::to_string(successfulFrames) + "/" + std::to_string(totalFrames) + " frames)"; logger::info("Multi-frame GIF capture completed: {} successful frames", successfulFrames); } else { result.message = "No frames were successfully captured"; logger::error("Multi-frame GIF capture failed: no successful frames"); } if (comInitialized) { CoUninitialize(); } } catch (const std::exception& e) { result.message = std::string("Exception: ") + e.what(); logger::error("Multi-frame GIF capture exception: {}", e.what()); } return result; } // Main capture function CaptureResult CaptureScreen(const CaptureParams& params) { // Handle multi-frame GIF separately if (params.format == ImageFormat::GIF_MULTIFRAME) { return CaptureMultiFrameGIF(params); } CaptureResult result; try { // Initialize COM HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); bool comInitialized = SUCCEEDED(hr); // Setup D3D11 and desktop duplication Microsoft::WRL::ComPtr<ID3D11Device> device; Microsoft::WRL::ComPtr<ID3D11DeviceContext> context; Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication; hr = SetupDesktopDuplication(device, context, duplication); if (FAILED(hr)) { result.message = "Failed to setup desktop duplication"; if (comInitialized) CoUninitialize(); return result; } // Capture single frame DirectX::ScratchImage frameImage; hr = CaptureSingleFrame(device.Get(), context.Get(), duplication.Get(), frameImage); if (FAILED(hr)) { result.message = "Failed to capture frame"; if (comInitialized) CoUninitialize(); return result; } // Convert to RGBA if needed (already handled in CaptureSingleFrame) DirectX::ScratchImage convertedImage; const DirectX::Image* img = frameImage.GetImage(0, 0, 0); if (!img) { result.message = "Failed to get image from frame"; if (comInitialized) CoUninitialize(); return result; } // Copy the image data to avoid issues with mapped memory hr = convertedImage.InitializeFromImage(*img); if (FAILED(hr)) { result.message = "Failed to copy image"; if (comInitialized) CoUninitialize(); return result; } // Generate filename with timestamp auto now = std::chrono::system_clock::now(); auto time_t_val = std::chrono::system_clock::to_time_t(now); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch()) % 1000; // Use localtime_s for safety std::tm timeinfo{}; localtime_s(&timeinfo, &time_t_val); std::wstringstream filename; filename << params.basePath; filename << L"_" << std::put_time(&timeinfo, L"%Y%m%d_%H%M%S"); filename << L"_" << std::setfill(L'0') << std::setw(3) << ms.count(); filename << GetFileExtension(params.format); result.filepath = filename.str(); // Save image based on format if (params.format == ImageFormat::DDS) { // Save as DDS using DirectXTex DXGI_FORMAT targetFormat = GetDXGIFormat(params.ddsMode); DirectX::ScratchImage compressedImage; if (DirectX::IsCompressed(targetFormat)) { hr = DirectX::Compress( convertedImage.GetImages(), convertedImage.GetImageCount(), convertedImage.GetMetadata(), targetFormat, DirectX::TEX_COMPRESS_DEFAULT, DirectX::TEX_THRESHOLD_DEFAULT, compressedImage ); if (FAILED(hr)) { result.message = "Failed to compress DDS image"; if (comInitialized) CoUninitialize(); return result; } hr = DirectX::SaveToDDSFile( compressedImage.GetImages(), compressedImage.GetImageCount(), compressedImage.GetMetadata(), DirectX::DDS_FLAGS_NONE, result.filepath.c_str() ); } else { hr = DirectX::SaveToDDSFile( convertedImage.GetImages(), convertedImage.GetImageCount(), convertedImage.GetMetadata(), DirectX::DDS_FLAGS_NONE, result.filepath.c_str() ); } if (FAILED(hr)) { result.message = "Failed to save DDS file"; if (comInitialized) CoUninitialize(); return result; } } else { // Save using WIC for other formats hr = SaveToWICFile(convertedImage, params, result.filepath); if (FAILED(hr)) { result.message = "Failed to save WIC file"; if (comInitialized) CoUninitialize(); return result; } } result.success = true; result.message = "Success"; if (comInitialized) { CoUninitialize(); } } catch (const std::exception& e) { result.message = std::string("Exception: ") + e.what(); logger::error("ScreenCapture exception: {}", e.what()); } catch (...) { result.message = "Unknown exception occurred"; logger::error("Unknown exception in ScreenCapture"); } return result; } // Helper function to save using WIC HRESULT SaveToWICFile(const DirectX::ScratchImage& image, const CaptureParams& params, const std::wstring& filepath) { // Initialize WIC Microsoft::WRL::ComPtr<IWICImagingFactory> wicFactory; HRESULT hr = CoCreateInstance( CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&wicFactory) ); if (FAILED(hr)) return hr; // Create encoder Microsoft::WRL::ComPtr<IWICBitmapEncoder> encoder; GUID containerFormat = GetWICCodec(params.format); hr = wicFactory->CreateEncoder(containerFormat, nullptr, &encoder); if (FAILED(hr)) return hr; // Create file stream Microsoft::WRL::ComPtr<IWICStream> stream; hr = wicFactory->CreateStream(&stream); if (FAILED(hr)) return hr; hr = stream->InitializeFromFilename(filepath.c_str(), GENERIC_WRITE); if (FAILED(hr)) return hr; hr = encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache); if (FAILED(hr)) return hr; // Create frame Microsoft::WRL::ComPtr<IWICBitmapFrameEncode> frame; Microsoft::WRL::ComPtr<IPropertyBag2> props; hr = encoder->CreateNewFrame(&frame, &props); if (FAILED(hr)) return hr; // Set properties for JPEG quality or TIFF compression if (params.format == ImageFormat::JPEG) { PROPBAG2 option = {}; option.pstrName = const_cast<LPOLESTR>(L"ImageQuality"); VARIANT varValue; VariantInit(&varValue); varValue.vt = VT_R4; varValue.fltVal = params.jpegQuality / 100.0f; props->Write(1, &option, &varValue); VariantClear(&varValue); } else if (params.format == ImageFormat::TIF) { PROPBAG2 option = {}; option.pstrName = const_cast<LPOLESTR>(L"TiffCompressionMethod"); VARIANT varValue; VariantInit(&varValue); varValue.vt = VT_UI1; varValue.bVal = static_cast<BYTE>(params.tiffMode); props->Write(1, &option, &varValue); VariantClear(&varValue); } hr = frame->Initialize(props.Get()); if (FAILED(hr)) return hr; // Get image data const DirectX::Image* img = image.GetImage(0, 0, 0); if (!img) return E_FAIL; // Set frame size and format hr = frame->SetSize(static_cast<UINT>(img->width), static_cast<UINT>(img->height)); if (FAILED(hr)) return hr; WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA; hr = frame->SetPixelFormat(&pixelFormat); if (FAILED(hr)) return hr; // Convert RGBA to BGRA if needed DirectX::ScratchImage convertedImage; if (img->format == DXGI_FORMAT_R8G8B8A8_UNORM) { // Convert RGBA to BGRA size_t imageSize = img->width * img->height * 4; std::vector<uint8_t> bgraData(imageSize); const uint8_t* src = img->pixels; uint8_t* dst = bgraData.data(); for (size_t i = 0; i < img->width * img->height; ++i) { dst[i * 4 + 0] = src[i * 4 + 2]; // B dst[i * 4 + 1] = src[i * 4 + 1]; // G dst[i * 4 + 2] = src[i * 4 + 0]; // R dst[i * 4 + 3] = src[i * 4 + 3]; // A } // Write pixels hr = frame->WritePixels( static_cast<UINT>(img->height), static_cast<UINT>(img->rowPitch), static_cast<UINT>(imageSize), bgraData.data() ); } else { // Write pixels directly hr = frame->WritePixels( static_cast<UINT>(img->height), static_cast<UINT>(img->rowPitch), static_cast<UINT>(img->slicePitch), img->pixels ); } if (FAILED(hr)) return hr; hr = frame->