Enable GIF support for ZoomIt (#43266)

Closes #43265
This commit is contained in:
Mario Hewardt
2025-11-10 11:57:13 -08:00
committed by GitHub
parent a33c484c93
commit a271a2f8af
14 changed files with 1411 additions and 468 deletions

View File

@@ -0,0 +1,548 @@
//==============================================================================
//
// Zoomit
// Sysinternals - www.sysinternals.com
//
// GIF recording support using Windows Imaging Component (WIC)
//
//==============================================================================
#include "pch.h"
#include "GifRecordingSession.h"
#include "CaptureFrameWait.h"
#include <shcore.h>
extern DWORD g_RecordScaling;
namespace winrt
{
using namespace Windows::Foundation;
using namespace Windows::Graphics;
using namespace Windows::Graphics::Capture;
using namespace Windows::Graphics::DirectX;
using namespace Windows::Graphics::DirectX::Direct3D11;
using namespace Windows::Storage;
using namespace Windows::UI::Composition;
}
namespace util
{
using namespace robmikh::common::uwp;
}
const float CLEAR_COLOR[] = { 0.0f, 0.0f, 0.0f, 1.0f };
int32_t EnsureEvenGif(int32_t value)
{
if (value % 2 == 0)
{
return value;
}
else
{
return value + 1;
}
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::GifRecordingSession
//
//----------------------------------------------------------------------------
GifRecordingSession::GifRecordingSession(
winrt::IDirect3DDevice const& device,
winrt::GraphicsCaptureItem const& item,
RECT const cropRect,
uint32_t frameRate,
winrt::Streams::IRandomAccessStream const& stream)
{
m_device = device;
m_d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(m_device);
m_d3dDevice->GetImmediateContext(m_d3dContext.put());
m_item = item;
m_frameRate = frameRate;
m_stream = stream;
auto itemSize = item.Size();
auto inputWidth = EnsureEvenGif(itemSize.Width);
auto inputHeight = EnsureEvenGif(itemSize.Height);
m_frameWait = std::make_shared<CaptureFrameWait>(m_device, m_item, winrt::SizeInt32{ inputWidth, inputHeight });
auto weakPointer{ std::weak_ptr{ m_frameWait } };
m_itemClosed = item.Closed(winrt::auto_revoke, [weakPointer](auto&, auto&)
{
auto sharedPointer{ weakPointer.lock() };
if (sharedPointer)
{
sharedPointer->StopCapture();
}
});
// Get crop dimension
if ((cropRect.right - cropRect.left) != 0)
{
m_rcCrop = cropRect;
m_frameWait->ShowCaptureBorder(false);
}
else
{
m_rcCrop.left = 0;
m_rcCrop.top = 0;
m_rcCrop.right = inputWidth;
m_rcCrop.bottom = inputHeight;
}
// Apply scaling
constexpr int c_minimumSize = 34;
auto scaledWidth = MulDiv(m_rcCrop.right - m_rcCrop.left, g_RecordScaling, 100);
auto scaledHeight = MulDiv(m_rcCrop.bottom - m_rcCrop.top, g_RecordScaling, 100);
m_width = scaledWidth;
m_height = scaledHeight;
if (m_width < c_minimumSize)
{
m_width = c_minimumSize;
m_height = MulDiv(m_height, m_width, scaledWidth);
}
if (m_height < c_minimumSize)
{
m_height = c_minimumSize;
m_width = MulDiv(m_width, m_height, scaledHeight);
}
if (m_width > inputWidth)
{
m_width = inputWidth;
m_height = c_minimumSize, MulDiv(m_height, scaledWidth, m_width);
}
if (m_height > inputHeight)
{
m_height = inputHeight;
m_width = c_minimumSize, MulDiv(m_width, scaledHeight, m_height);
}
m_width = EnsureEvenGif(m_width);
m_height = EnsureEvenGif(m_height);
m_frameDelay = (frameRate > 0) ? (100 / frameRate) : 15;
// Initialize WIC
winrt::check_hresult(CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(m_wicFactory.put())));
// Create WIC stream from IRandomAccessStream
winrt::check_hresult(m_wicFactory->CreateStream(m_wicStream.put()));
// Get the IStream from the IRandomAccessStream
winrt::com_ptr<IStream> streamInterop;
winrt::check_hresult(CreateStreamOverRandomAccessStream(
winrt::get_unknown(stream),
IID_PPV_ARGS(streamInterop.put())));
winrt::check_hresult(m_wicStream->InitializeFromIStream(streamInterop.get()));
// Create GIF encoder
winrt::check_hresult(m_wicFactory->CreateEncoder(
GUID_ContainerFormatGif,
nullptr,
m_gifEncoder.put()));
winrt::check_hresult(m_gifEncoder->Initialize(m_wicStream.get(), WICBitmapEncoderNoCache));
// Set global GIF metadata for looping (NETSCAPE2.0 application extension)
try
{
winrt::com_ptr<IWICMetadataQueryWriter> encoderMetadataWriter;
if (SUCCEEDED(m_gifEncoder->GetMetadataQueryWriter(encoderMetadataWriter.put())) && encoderMetadataWriter)
{
OutputDebugStringW(L"Setting NETSCAPE2.0 looping extension on encoder...\n");
// Set application extension
PROPVARIANT propValue;
PropVariantInit(&propValue);
propValue.vt = VT_UI1 | VT_VECTOR;
propValue.caub.cElems = 11;
propValue.caub.pElems = static_cast<UCHAR*>(CoTaskMemAlloc(11));
if (propValue.caub.pElems != nullptr)
{
memcpy(propValue.caub.pElems, "NETSCAPE2.0", 11);
HRESULT hr = encoderMetadataWriter->SetMetadataByName(L"/appext/application", &propValue);
if (SUCCEEDED(hr))
{
OutputDebugStringW(L"Encoder application extension set successfully\n");
}
else
{
OutputDebugStringW(L"Failed to set encoder application extension\n");
}
PropVariantClear(&propValue);
// Set loop count (0 = infinite)
PropVariantInit(&propValue);
propValue.vt = VT_UI1 | VT_VECTOR;
propValue.caub.cElems = 5;
propValue.caub.pElems = static_cast<UCHAR*>(CoTaskMemAlloc(5));
if (propValue.caub.pElems != nullptr)
{
propValue.caub.pElems[0] = 3;
propValue.caub.pElems[1] = 1;
propValue.caub.pElems[2] = 0;
propValue.caub.pElems[3] = 0;
propValue.caub.pElems[4] = 0;
hr = encoderMetadataWriter->SetMetadataByName(L"/appext/data", &propValue);
if (SUCCEEDED(hr))
{
OutputDebugStringW(L"Encoder loop count set successfully\n");
}
else
{
OutputDebugStringW(L"Failed to set encoder loop count\n");
}
PropVariantClear(&propValue);
}
}
}
else
{
OutputDebugStringW(L"Failed to get encoder metadata writer\n");
}
}
catch (...)
{
OutputDebugStringW(L"Warning: Failed to set GIF encoder looping metadata\n");
}
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::~GifRecordingSession
//
//----------------------------------------------------------------------------
GifRecordingSession::~GifRecordingSession()
{
Close();
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::Create
//
//----------------------------------------------------------------------------
std::shared_ptr<GifRecordingSession> GifRecordingSession::Create(
winrt::IDirect3DDevice const& device,
winrt::GraphicsCaptureItem const& item,
RECT const& crop,
uint32_t frameRate,
winrt::Streams::IRandomAccessStream const& stream)
{
return std::shared_ptr<GifRecordingSession>(new GifRecordingSession(device, item, crop, frameRate, stream));
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::EncodeFrame
//
//----------------------------------------------------------------------------
HRESULT GifRecordingSession::EncodeFrame(ID3D11Texture2D* frameTexture)
{
try
{
// Create a staging texture for CPU access
D3D11_TEXTURE2D_DESC frameDesc;
frameTexture->GetDesc(&frameDesc);
// GIF encoding with palette generation is VERY slow at high resolutions (4K takes 1 second per frame!)
UINT targetWidth = frameDesc.Width;
UINT targetHeight = frameDesc.Height;
if (frameDesc.Width > static_cast<uint32_t>(m_width) || frameDesc.Height > static_cast<uint32_t>(m_height))
{
float scaleX = static_cast<float>(m_width) / frameDesc.Width;
float scaleY = static_cast<float>(m_height) / frameDesc.Height;
float scale = min(scaleX, scaleY);
targetWidth = static_cast<UINT>(frameDesc.Width * scale);
targetHeight = static_cast<UINT>(frameDesc.Height * scale);
// Ensure even dimensions for GIF
targetWidth = (targetWidth / 2) * 2;
targetHeight = (targetHeight / 2) * 2;
}
D3D11_TEXTURE2D_DESC stagingDesc = frameDesc;
stagingDesc.Usage = D3D11_USAGE_STAGING;
stagingDesc.BindFlags = 0;
stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
stagingDesc.MiscFlags = 0;
winrt::com_ptr<ID3D11Texture2D> stagingTexture;
winrt::check_hresult(m_d3dDevice->CreateTexture2D(&stagingDesc, nullptr, stagingTexture.put()));
// Copy the frame to staging texture
m_d3dContext->CopyResource(stagingTexture.get(), frameTexture);
// Map the staging texture
D3D11_MAPPED_SUBRESOURCE mappedResource;
winrt::check_hresult(m_d3dContext->Map(stagingTexture.get(), 0, D3D11_MAP_READ, 0, &mappedResource));
// Create a new frame in the GIF
winrt::com_ptr<IWICBitmapFrameEncode> frameEncode;
winrt::com_ptr<IPropertyBag2> propertyBag;
winrt::check_hresult(m_gifEncoder->CreateNewFrame(frameEncode.put(), propertyBag.put()));
// Initialize the frame encoder with property bag
winrt::check_hresult(frameEncode->Initialize(propertyBag.get()));
// CRITICAL: For GIF, we MUST set size and pixel format BEFORE WriteSource
// Use target dimensions (may be downsampled)
winrt::check_hresult(frameEncode->SetSize(targetWidth, targetHeight));
// Set the pixel format to 8-bit indexed (required for GIF)
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat8bppIndexed;
winrt::check_hresult(frameEncode->SetPixelFormat(&pixelFormat));
// Create a WIC bitmap from the BGRA texture data
winrt::com_ptr<IWICBitmap> sourceBitmap;
winrt::check_hresult(m_wicFactory->CreateBitmapFromMemory(
frameDesc.Width,
frameDesc.Height,
GUID_WICPixelFormat32bppBGRA,
mappedResource.RowPitch,
frameDesc.Height * mappedResource.RowPitch,
static_cast<BYTE*>(mappedResource.pData),
sourceBitmap.put()));
// If we need downsampling, use WIC scaler
winrt::com_ptr<IWICBitmapSource> finalSource = sourceBitmap;
if (targetWidth != frameDesc.Width || targetHeight != frameDesc.Height)
{
winrt::com_ptr<IWICBitmapScaler> scaler;
winrt::check_hresult(m_wicFactory->CreateBitmapScaler(scaler.put()));
winrt::check_hresult(scaler->Initialize(
sourceBitmap.get(),
targetWidth,
targetHeight,
WICBitmapInterpolationModeHighQualityCubic));
finalSource = scaler;
OutputDebugStringW((L"Downsampled from " + std::to_wstring(frameDesc.Width) + L"x" + std::to_wstring(frameDesc.Height) +
L" to " + std::to_wstring(targetWidth) + L"x" + std::to_wstring(targetHeight) + L"\n").c_str());
}
// Use WriteSource - WIC will handle the BGRA to 8bpp indexed conversion
winrt::check_hresult(frameEncode->WriteSource(finalSource.get(), nullptr));
try
{
winrt::com_ptr<IWICMetadataQueryWriter> frameMetadataWriter;
if (SUCCEEDED(frameEncode->GetMetadataQueryWriter(frameMetadataWriter.put())) && frameMetadataWriter)
{
// Set the frame delay in the metadata (in hundredths of a second)
PROPVARIANT propValue;
PropVariantInit(&propValue);
propValue.vt = VT_UI2;
propValue.uiVal = static_cast<USHORT>(m_frameDelay);
frameMetadataWriter->SetMetadataByName(L"/grctlext/Delay", &propValue);
PropVariantClear(&propValue);
// Set disposal method (2 = restore to background, needed for animation)
PropVariantInit(&propValue);
propValue.vt = VT_UI1;
propValue.bVal = 2; // Disposal method: restore to background color
frameMetadataWriter->SetMetadataByName(L"/grctlext/Disposal", &propValue);
PropVariantClear(&propValue);
}
}
catch (...)
{
// Metadata setting failed, continue anyway
OutputDebugStringW(L"Warning: Failed to set GIF frame metadata\n");
}
// Commit the frame
OutputDebugStringW(L"About to commit frame to encoder...\n");
winrt::check_hresult(frameEncode->Commit());
OutputDebugStringW(L"Frame committed successfully\n");
// Unmap the staging texture
m_d3dContext->Unmap(stagingTexture.get(), 0);
// Increment and log frame count
m_frameCount++;
OutputDebugStringW((L"GIF Frame #" + std::to_wstring(m_frameCount) + L" fully encoded and committed\n").c_str());
return S_OK;
}
catch (const winrt::hresult_error& error)
{
OutputDebugStringW(error.message().c_str());
return error.code();
}
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::StartAsync
//
//----------------------------------------------------------------------------
winrt::IAsyncAction GifRecordingSession::StartAsync()
{
auto expected = false;
if (m_isRecording.compare_exchange_strong(expected, true))
{
auto self = shared_from_this();
try
{
// Start capturing frames
auto frameStartTime = std::chrono::high_resolution_clock::now();
int captureAttempts = 0;
int successfulCaptures = 0;
int duplicatedFrames = 0;
// Keep track of the last frame to duplicate when needed
winrt::com_ptr<ID3D11Texture2D> lastCroppedTexture;
while (m_isRecording && !m_closed)
{
captureAttempts++;
auto frame = m_frameWait->TryGetNextFrame();
winrt::com_ptr<ID3D11Texture2D> croppedTexture;
if (frame)
{
successfulCaptures++;
auto contentSize = frame->ContentSize;
auto frameTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame->FrameTexture);
D3D11_TEXTURE2D_DESC desc = {};
frameTexture->GetDesc(&desc);
// Use the smaller of the crop size or content size
auto width = min(m_rcCrop.right - m_rcCrop.left, contentSize.Width);
auto height = min(m_rcCrop.bottom - m_rcCrop.top, contentSize.Height);
D3D11_TEXTURE2D_DESC croppedDesc = {};
croppedDesc.Width = width;
croppedDesc.Height = height;
croppedDesc.MipLevels = 1;
croppedDesc.ArraySize = 1;
croppedDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
croppedDesc.SampleDesc.Count = 1;
croppedDesc.Usage = D3D11_USAGE_DEFAULT;
croppedDesc.BindFlags = D3D11_BIND_RENDER_TARGET;
winrt::check_hresult(m_d3dDevice->CreateTexture2D(&croppedDesc, nullptr, croppedTexture.put()));
// Set the content region to copy and clamp the coordinates
D3D11_BOX region = {};
region.left = std::clamp(m_rcCrop.left, static_cast<LONG>(0), static_cast<LONG>(desc.Width));
region.right = std::clamp(m_rcCrop.left + width, static_cast<LONG>(0), static_cast<LONG>(desc.Width));
region.top = std::clamp(m_rcCrop.top, static_cast<LONG>(0), static_cast<LONG>(desc.Height));
region.bottom = std::clamp(m_rcCrop.top + height, static_cast<LONG>(0), static_cast<LONG>(desc.Height));
region.back = 1;
// Copy the cropped region
m_d3dContext->CopySubresourceRegion(
croppedTexture.get(),
0,
0, 0, 0,
frameTexture.get(),
0,
&region);
// Save this as the last frame for duplication
lastCroppedTexture = croppedTexture;
}
else if (lastCroppedTexture)
{
// No new frame, duplicate the last one
duplicatedFrames++;
croppedTexture = lastCroppedTexture;
}
// Encode the frame (either new or duplicated)
if (croppedTexture)
{
HRESULT hr = EncodeFrame(croppedTexture.get());
if (FAILED(hr))
{
CloseInternal();
break;
}
}
// Wait for the next frame interval
co_await winrt::resume_after(std::chrono::milliseconds(1000 / m_frameRate));
}
// Commit the GIF encoder
if (m_gifEncoder)
{
auto frameEndTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(frameEndTime - frameStartTime).count();
OutputDebugStringW(L"Recording stopped. Committing GIF encoder...\n");
OutputDebugStringW((L"Total frames captured: " + std::to_wstring(m_frameCount) + L"\n").c_str());
OutputDebugStringW((L"Capture attempts: " + std::to_wstring(captureAttempts) + L"\n").c_str());
OutputDebugStringW((L"Successful captures: " + std::to_wstring(successfulCaptures) + L"\n").c_str());
OutputDebugStringW((L"Duplicated frames: " + std::to_wstring(duplicatedFrames) + L"\n").c_str());
OutputDebugStringW((L"Recording duration: " + std::to_wstring(duration) + L"ms\n").c_str());
OutputDebugStringW((L"Actual FPS: " + std::to_wstring(m_frameCount * 1000.0 / duration) + L"\n").c_str());
winrt::check_hresult(m_gifEncoder->Commit());
OutputDebugStringW(L"GIF encoder committed successfully\n");
}
}
catch (const winrt::hresult_error& error)
{
OutputDebugStringW(L"Error in GIF recording: ");
OutputDebugStringW(error.message().c_str());
OutputDebugStringW(L"\n");
// Try to commit the encoder even on error
if (m_gifEncoder)
{
try
{
m_gifEncoder->Commit();
}
catch (...) {}
}
CloseInternal();
}
}
co_return;
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::Close
//
//----------------------------------------------------------------------------
void GifRecordingSession::Close()
{
auto expected = false;
if (m_closed.compare_exchange_strong(expected, true))
{
expected = true;
if (!m_isRecording.compare_exchange_strong(expected, false))
{
CloseInternal();
}
else
{
m_frameWait->StopCapture();
}
}
}
//----------------------------------------------------------------------------
//
// GifRecordingSession::CloseInternal
//
//----------------------------------------------------------------------------
void GifRecordingSession::CloseInternal()
{
m_frameWait->StopCapture();
m_itemClosed.revoke();
}

View File

@@ -0,0 +1,69 @@
//==============================================================================
//
// Zoomit
// Sysinternals - www.sysinternals.com
//
// GIF recording support using Windows Imaging Component (WIC)
//
//==============================================================================
#pragma once
#include "CaptureFrameWait.h"
#include <d3d11_4.h>
#include <vector>
class GifRecordingSession : public std::enable_shared_from_this<GifRecordingSession>
{
public:
[[nodiscard]] static std::shared_ptr<GifRecordingSession> Create(
winrt::Direct3D11::IDirect3DDevice const& device,
winrt::GraphicsCaptureItem const& item,
RECT const& cropRect,
uint32_t frameRate,
winrt::Streams::IRandomAccessStream const& stream);
~GifRecordingSession();
winrt::IAsyncAction StartAsync();
void EnableCursorCapture(bool enable = true) { m_frameWait->EnableCursorCapture(enable); }
void Close();
private:
GifRecordingSession(
winrt::Direct3D11::IDirect3DDevice const& device,
winrt::Capture::GraphicsCaptureItem const& item,
RECT const cropRect,
uint32_t frameRate,
winrt::Streams::IRandomAccessStream const& stream);
void CloseInternal();
HRESULT EncodeFrame(ID3D11Texture2D* texture);
private:
winrt::Direct3D11::IDirect3DDevice m_device{ nullptr };
winrt::com_ptr<ID3D11Device> m_d3dDevice;
winrt::com_ptr<ID3D11DeviceContext> m_d3dContext;
RECT m_rcCrop;
uint32_t m_frameRate;
winrt::GraphicsCaptureItem m_item{ nullptr };
winrt::GraphicsCaptureItem::Closed_revoker m_itemClosed;
std::shared_ptr<CaptureFrameWait> m_frameWait;
winrt::Streams::IRandomAccessStream m_stream{ nullptr };
// WIC components for GIF encoding
winrt::com_ptr<IWICImagingFactory> m_wicFactory;
winrt::com_ptr<IWICStream> m_wicStream;
winrt::com_ptr<IWICBitmapEncoder> m_gifEncoder;
winrt::com_ptr<IWICMetadataQueryWriter> m_encoderMetadataWriter;
std::atomic<bool> m_isRecording = false;
std::atomic<bool> m_closed = false;
uint32_t m_frameWidth=0;
uint32_t m_frameHeight=0;
uint32_t m_frameDelay=0;
uint32_t m_frameCount = 0;
int32_t m_width=0;
int32_t m_height=0;
};

View File

@@ -32,18 +32,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// TEXTINCLUDE
//
1 TEXTINCLUDE
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
3 TEXTINCLUDE
BEGIN
"#include ""binres.rc""\0"
END
@@ -121,8 +121,8 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
BEGIN
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
LTEXT "ZoomIt v9.10",IDC_VERSION,42,7,73,10
LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
LTEXT "ZoomIt v9.20",IDC_VERSION,42,7,73,10
LTEXT "Copyright <EFBFBD> 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
"SysLink",WS_TABSTOP,42,26,150,9
ICON "APPICON",IDC_STATIC,12,9,20,20
@@ -272,13 +272,15 @@ BEGIN
LTEXT "Note: Recording is only available on Windows 10 (version 1903) and higher.",IDC_STATIC,7,77,246,19
LTEXT "Scaling:",IDC_STATIC,30,115,26,8
COMBOBOX IDC_RECORD_SCALING,61,114,26,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | WS_VSCROLL | WS_TABSTOP
LTEXT "Format:",IDC_STATIC,30,132,26,8
COMBOBOX IDC_RECORD_FORMAT,61,131,60,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | WS_VSCROLL | WS_TABSTOP
LTEXT "Frame Rate:",IDC_STATIC,119,115,44,8,NOT WS_VISIBLE
COMBOBOX IDC_RECORD_FRAME_RATE,166,114,42,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
LTEXT "To crop the portion of the screen that will be recorded, enter the hotkey with the Shift key in the opposite mode. ",IDC_STATIC,7,32,246,19
LTEXT "To record a specific window, enter the hotkey with the Alt key in the opposite mode.",IDC_STATIC,7,55,246,19
CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,137,83,10
COMBOBOX IDC_MICROPHONE,81,152,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Microphone:",IDC_STATIC,32,154,47,8
CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,149,83,10
COMBOBOX IDC_MICROPHONE,81,164,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Microphone:",IDC_STATIC,32,166,47,8
END
SNIP DIALOGEX 0, 0, 260, 68

View File

@@ -234,6 +234,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="GifRecordingSession.cpp" />
<ClCompile Include="pch.cpp" />
<ClCompile Include="SelectRectangle.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
@@ -288,6 +289,7 @@
<ClInclude Include="AudioSampleGenerator.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\Eula\Eula.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)..\ZoomItModuleInterface\Trace.h" />
<ClInclude Include="GifRecordingSession.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="Registry.h" />
<ClInclude Include="resource.h" />

View File

@@ -54,6 +54,9 @@
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\WindowsVersions.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="GifRecordingSession.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Registry.h">
@@ -95,6 +98,9 @@
<ClInclude Include="ZoomItSettings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="GifRecordingSession.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="appicon.ico">

View File

@@ -3,6 +3,13 @@
#include "Registry.h"
#include "DemoType.h"
// Recording format enum
enum class RecordingFormat
{
GIF = 0,
MP4 = 1
};
DWORD g_ToggleKey = (HOTKEYF_CONTROL << 8)| '1';
DWORD g_LiveZoomToggleKey = ((HOTKEYF_CONTROL) << 8)| '4';
DWORD g_DrawToggleKey = ((HOTKEYF_CONTROL) << 8)| '2';
@@ -38,8 +45,10 @@ BOOLEAN g_DemoTypeUserDriven = false;
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
DWORD g_DemoTypeSpeedSlider = static_cast<int>(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED);
DWORD g_RecordFrameRate = 30;
// Divide by 100 to get actual scaling
DWORD g_RecordScaling = 100;
DWORD g_RecordScaling = 100;
DWORD g_RecordScalingGIF = 50;
DWORD g_RecordScalingMP4 = 100;
RecordingFormat g_RecordingFormat = RecordingFormat::GIF;
BOOLEAN g_CaptureAudio = FALSE;
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
@@ -79,7 +88,9 @@ REG_SETTING RegSettings[] = {
{ L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast<DOUBLE>(g_SliderZoomLevel) },
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast<DOUBLE>(0) },
{ L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast<DOUBLE>(g_RecordFrameRate) },
{ L"RecordScaling", SETTING_TYPE_DWORD, 0, &g_RecordScaling, static_cast<DOUBLE>(g_RecordScaling) },
{ L"RecordingFormat", SETTING_TYPE_DWORD, 0, &g_RecordingFormat, static_cast<DOUBLE>(0) },
{ L"RecordScalingGIF", SETTING_TYPE_DWORD, 0, &g_RecordScalingGIF, static_cast<DOUBLE>(g_RecordScalingGIF) },
{ L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast<DOUBLE>(g_RecordScalingMP4) },
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) },
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast<DOUBLE>(0) },
{ NULL, SETTING_TYPE_DWORD, 0, NULL, static_cast<DOUBLE>(0) }

File diff suppressed because it is too large Load Diff

View File

@@ -93,6 +93,7 @@
#define IDC_DEMOTYPE_SLIDER2 1074
#define IDC_DEMOTYPE_STATIC2 1074
#define IDC_COPYRIGHT 1075
#define IDC_RECORD_FORMAT 1076
#define IDC_PEN_WIDTH 1105
#define IDC_TIMER 1106
#define IDC_SMOOTH_IMAGE 1107

View File

@@ -14,6 +14,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
const unsigned int SPECIAL_SEMANTICS_SHORTCUT = 1;
const unsigned int SPECIAL_SEMANTICS_COLOR = 2;
const unsigned int SPECIAL_SEMANTICS_LOG_FONT = 3;
const unsigned int SPECIAL_SEMANTICS_RECORDING_FORMAT = 4;
const unsigned int SPECIAL_SEMANTICS_RECORD_SCALING_GIF = 5;
const unsigned int SPECIAL_SEMANTICS_RECORD_SCALING_MP4 = 6;
std::vector<unsigned char> base64_decode(const std::wstring& base64_string)
{
@@ -72,6 +75,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
{ L"PenColor", SPECIAL_SEMANTICS_COLOR },
{ L"BreakPenColor", SPECIAL_SEMANTICS_COLOR },
{ L"Font", SPECIAL_SEMANTICS_LOG_FONT },
{ L"RecordingFormat", SPECIAL_SEMANTICS_RECORDING_FORMAT },
{ L"RecordScalingGIF", SPECIAL_SEMANTICS_RECORD_SCALING_GIF },
{ L"RecordScalingMP4", SPECIAL_SEMANTICS_RECORD_SCALING_MP4 },
};
hstring ZoomItSettings::LoadSettingsJson()
@@ -103,6 +109,11 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
value & 0xFF);
_settings.add_property(curSetting->ValueName, hotkey.get_json());
}
else if (special_semantics->second == SPECIAL_SEMANTICS_RECORDING_FORMAT)
{
std::wstring formatString = (value == 0) ? L"GIF" : L"MP4";
_settings.add_property(L"RecordFormat", formatString);
}
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
{
/* PowerToys settings likes colors as #FFFFFF strings.
@@ -156,6 +167,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
curSetting++;
}
DWORD recordScaling = (g_RecordingFormat == static_cast<RecordingFormat>(0)) ? g_RecordScalingGIF : g_RecordScalingMP4;
_settings.add_property<DWORD>(L"RecordScaling", recordScaling);
return _settings.get_raw_json().Stringify();
}
@@ -167,6 +181,8 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
PowerToysSettings::PowerToyValues valuesFromSettings =
PowerToysSettings::PowerToyValues::from_json_string(json, L"ZoomIt");
bool formatChanged = false;
PREG_SETTING curSetting = RegSettings;
while (curSetting->ValueName)
{
@@ -212,6 +228,42 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
*static_cast<PDWORD>(curSetting->Setting) = value;
}
}
else if (special_semantics->second == SPECIAL_SEMANTICS_RECORDING_FORMAT)
{
// Convert string ("GIF" or "MP4") to DWORD enum value (0=GIF, 1=MP4)
auto possibleValue = valuesFromSettings.get_string_value(L"RecordFormat");
if (possibleValue.has_value())
{
RecordingFormat oldFormat = g_RecordingFormat;
DWORD formatValue = (possibleValue.value() == L"GIF") ? 0 : 1;
RecordingFormat newFormat = static_cast<RecordingFormat>(formatValue);
*static_cast<PDWORD>(curSetting->Setting) = formatValue;
if (oldFormat != newFormat)
{
formatChanged = true;
if (oldFormat == static_cast<RecordingFormat>(0))
{
g_RecordScalingGIF = g_RecordScaling;
}
else
{
g_RecordScalingMP4 = g_RecordScaling;
}
if (newFormat == static_cast<RecordingFormat>(0))
{
g_RecordScaling = g_RecordScalingGIF;
}
else
{
g_RecordScaling = g_RecordScalingMP4;
}
}
}
}
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
{
/* PowerToys settings likes colors as #FFFFFF strings.
@@ -275,6 +327,22 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
}
curSetting++;
}
auto recordScalingValue = valuesFromSettings.get_uint_value(L"RecordScaling");
if (recordScalingValue.has_value() && !formatChanged)
{
g_RecordScaling = recordScalingValue.value();
if (g_RecordingFormat == static_cast<RecordingFormat>(0))
{
g_RecordScalingGIF = recordScalingValue.value();
}
else
{
g_RecordScalingMP4 = recordScalingValue.value();
}
}
reg.WriteRegSettings(RegSettings);
}
}