Compare commits

...

1 Commits

Author SHA1 Message Date
MarioHewardt
2024b269a7 Experimental panoramic screen capture 2026-01-17 16:49:32 -08:00
9 changed files with 1528 additions and 12 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,169 @@
//==============================================================================
//
// Zoomit
// Sysinternals - www.sysinternals.com
//
// Panoramic screenshot capture and stitching
//
//==============================================================================
#pragma once
#include <windows.h>
#include <vector>
#include <atomic>
#include <memory>
// WIL for unique handles
#include <wil/resource.h>
// Message to signal panorama capture stop (must match ZoomIt.h)
#define WM_USER_PANORAMA_STOP WM_USER+500
// Forward declarations
struct ID3D11Device;
struct ID3D11DeviceContext;
struct ID3D11Texture2D;
//----------------------------------------------------------------------------
// Structure to hold a captured frame with its position
//----------------------------------------------------------------------------
struct PanoramaFrame
{
std::vector<BYTE> pixels; // BGRA pixel data
int width; // Frame width
int height; // Frame height
int relativeX; // X position relative to first frame
int relativeY; // Y position relative to first frame
LONGLONG timestamp; // Capture timestamp
};
//----------------------------------------------------------------------------
// Structure for scroll offset detection result
//----------------------------------------------------------------------------
struct ScrollOffset
{
int dx; // Horizontal scroll amount
int dy; // Vertical scroll amount
double confidence; // Match confidence (0.0 - 1.0)
bool valid; // Whether a valid match was found
};
//----------------------------------------------------------------------------
// PanoramaCapture class - handles continuous capture and stitching
//----------------------------------------------------------------------------
class PanoramaCapture
{
public:
PanoramaCapture();
~PanoramaCapture();
// Start panorama capture mode with a selected rectangle
// Returns true if capture started successfully
bool Start(HWND ownerWindow, const RECT& captureRect);
// Stop capture and return stitched result as HBITMAP
// Caller is responsible for deleting the returned bitmap
HBITMAP Stop();
// Cancel capture without producing result
void Cancel();
// Check if capture is currently active
bool IsCapturing() const { return m_capturing; }
// Get the overlay window handle (for message routing)
HWND GetOverlayWindow() const { return m_overlayWindow.get(); }
// Get current frame count
size_t GetFrameCount() const { return m_frames.size(); }
// Force a frame capture (called by timer or manually)
void CaptureFrame();
private:
// Detect scroll direction and amount between two frames
// Uses normalized cross-correlation for accurate matching
ScrollOffset DetectScrollOffset(
const std::vector<BYTE>& frame1,
const std::vector<BYTE>& frame2,
int width, int height);
// Compute normalized cross-correlation between two image regions
double ComputeNCC(
const BYTE* img1, const BYTE* img2,
int width, int height, int stride,
int img1OffsetX, int img1OffsetY,
int img2OffsetX, int img2OffsetY,
int compareWidth, int compareHeight);
// Check if two frames are significantly different
bool FramesAreDifferent(
const std::vector<BYTE>& frame1,
const std::vector<BYTE>& frame2,
int width, int height);
// Stitch all captured frames into final panorama
HBITMAP StitchFrames();
// Blend a frame onto the canvas - only copy new (non-overlapping) content
void BlendFrameOntoCanvas(
BYTE* canvas, int canvasWidth, int canvasHeight,
const BYTE* frame, int frameWidth, int frameHeight,
int destX, int destY, int previousFrameBottom);
// Capture screen region to byte array
std::vector<BYTE> CaptureScreenRegion(const RECT& rect, int& outWidth, int& outHeight);
// Create and manage the overlay window
bool CreateOverlayWindow(HWND ownerWindow);
static LRESULT CALLBACK OverlayWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT HandleOverlayMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
// Timer callback for periodic frame capture
static void CALLBACK TimerCallback(HWND hwnd, UINT msg, UINT_PTR idTimer, DWORD dwTime);
// Keyboard hook for ESC detection
static LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam);
bool InstallKeyboardHook();
void RemoveKeyboardHook();
private:
// Capture state
std::atomic<bool> m_capturing;
RECT m_captureRect;
HWND m_ownerWindow;
wil::unique_hwnd m_overlayWindow;
// Captured frames
std::vector<PanoramaFrame> m_frames;
std::vector<BYTE> m_previousFrame;
int m_previousWidth;
int m_previousHeight;
// Accumulated scroll offsets
int m_totalOffsetX;
int m_totalOffsetY;
// Timer for periodic capture
UINT_PTR m_timerID;
static const UINT CAPTURE_INTERVAL_MS = 100; // Capture every 100ms
// Detection parameters - reduced for performance
static const int SEARCH_RANGE_Y = 150; // Max vertical search range
static const int SEARCH_RANGE_X = 30; // Max horizontal search range
static const int SEARCH_STEP = 8; // Step size for search (larger = faster)
static const int STRIP_HEIGHT = 40; // Height of comparison strip
static const int MIN_SCROLL_THRESHOLD = 5; // Minimum pixels to consider as scroll
static const double MATCH_THRESHOLD; // NCC threshold for valid match
static const double DIFFERENCE_THRESHOLD; // Threshold for frame difference
// Window class name
static const wchar_t* OVERLAY_CLASS_NAME;
// Instance pointer for static callbacks
static PanoramaCapture* s_instance;
// Keyboard hook handle
HHOOK m_keyboardHook;
};

View File

@@ -66,10 +66,11 @@ type_pEnableThemeDialogTexture pEnableThemeDialogTexture;
#define WM_USER_MAGNIFY_CURSOR WM_USER+108
#define WM_USER_EXIT_MODE WM_USER+109
#define WM_USER_RELOAD_SETTINGS WM_USER+110
#define WM_USER_PANORAMA_STOP WM_USER+500
typedef struct _TYPED_KEY {
RECT rc;
struct _TYPED_KEY *Next;
struct _TYPED_KEY *Next;
} TYPED_KEY, *P_TYPED_KEY;
typedef struct _DRAW_UNDO {
@@ -108,17 +109,17 @@ typedef struct {
typedef BOOL (__stdcall *type_pGetMonitorInfo)(
HMONITOR hMonitor, // handle to display monitor
LPMONITORINFO lpmi // display monitor information
LPMONITORINFO lpmi // display monitor information
);
typedef HMONITOR (__stdcall *type_MonitorFromPoint)(
POINT pt, // point
POINT pt, // point
DWORD dwFlags // determine return value
);
typedef HRESULT (__stdcall *type_pSHAutoComplete)(
HWND hwndEdit,
DWORD dwFlags
DWORD dwFlags
);
// DPI awareness
@@ -151,7 +152,7 @@ typedef BOOL(__stdcall *type_pMagSetWindowFilterList)(
HWND* pHWND
);
typedef BOOL(__stdcall* type_pMagSetLensUseBitmapSmoothing)(
_In_ HWND,
_In_ HWND,
_In_ BOOL
);
typedef BOOL(__stdcall* type_MagSetFullscreenUseBitmapSmoothing)(
@@ -169,7 +170,7 @@ typedef BOOL(__stdcall *type_pGetPointerPenInfo)(
_Out_ POINTER_PEN_INFO *penInfo
);
typedef HRESULT (__stdcall *type_pDwmIsCompositionEnabled)(
typedef HRESULT (__stdcall *type_pDwmIsCompositionEnabled)(
BOOL *pfEnabled
);
@@ -182,7 +183,7 @@ typedef BOOL (__stdcall *type_pSetLayeredWindowAttributes)(
);
// Presentation mode check
typedef HRESULT (__stdcall *type_pSHQueryUserNotificationState)(
typedef HRESULT (__stdcall *type_pSHQueryUserNotificationState)(
QUERY_USER_NOTIFICATION_STATE *pQueryUserNotificationState
);

View File

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

View File

@@ -57,6 +57,9 @@
<ClCompile Include="GifRecordingSession.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PanoramaCapture.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Registry.h">
@@ -98,6 +101,9 @@
<ClInclude Include="ZoomItSettings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PanoramaCapture.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="GifRecordingSession.h">
<Filter>Header Files</Filter>
</ClInclude>

View File

@@ -17,6 +17,7 @@ DWORD g_BreakToggleKey = ((HOTKEYF_CONTROL) << 8)| '3';
DWORD g_DemoTypeToggleKey = ((HOTKEYF_CONTROL) << 8) | '7';
DWORD g_RecordToggleKey = ((HOTKEYF_CONTROL) << 8) | '5';
DWORD g_SnipToggleKey = ((HOTKEYF_CONTROL) << 8) | '6';
DWORD g_PanoramaToggleKey = ((HOTKEYF_CONTROL) << 8) | '9';
DWORD g_ShowExpiredTime = 1;
DWORD g_SliderZoomLevel = 3;
@@ -58,6 +59,7 @@ REG_SETTING RegSettings[] = {
{ L"DrawToggleKey", SETTING_TYPE_DWORD, 0, &g_DrawToggleKey, static_cast<DOUBLE>(g_DrawToggleKey) },
{ L"RecordToggleKey", SETTING_TYPE_DWORD, 0, &g_RecordToggleKey, static_cast<DOUBLE>(g_RecordToggleKey) },
{ L"SnipToggleKey", SETTING_TYPE_DWORD, 0, &g_SnipToggleKey, static_cast<DOUBLE>(g_SnipToggleKey) },
{ L"PanoramaToggleKey", SETTING_TYPE_DWORD, 0, &g_PanoramaToggleKey, static_cast<DOUBLE>(g_PanoramaToggleKey) },
{ L"PenColor", SETTING_TYPE_DWORD, 0, &g_PenColor, static_cast<DOUBLE>(g_PenColor) },
{ L"PenWidth", SETTING_TYPE_DWORD, 0, &g_RootPenWidth, static_cast<DOUBLE>(g_RootPenWidth) },
{ L"OptionsShown", SETTING_TYPE_BOOLEAN, 0, &g_OptionsShown, static_cast<DOUBLE>(g_OptionsShown) },

View File

@@ -85,6 +85,8 @@ COLORREF g_CustomColors[16];
#define DEMOTYPE_RESET_HOTKEY 11
#define RECORD_GIF_HOTKEY 12
#define RECORD_GIF_WINDOW_HOTKEY 13
#define PANORAMA_HOTKEY 14
#define PANORAMA_SAVE_HOTKEY 15
#define ZOOM_PAGE 0
#define LIVE_PAGE 1
@@ -148,6 +150,7 @@ DWORD g_BreakToggleMod;
DWORD g_DemoTypeToggleMod;
DWORD g_RecordToggleMod;
DWORD g_SnipToggleMod;
DWORD g_PanoramaToggleMod;
BOOLEAN g_ZoomOnLiveZoom = FALSE;
DWORD g_PenWidth = PEN_WIDTH;
@@ -186,6 +189,12 @@ std::wstring g_RecordingSaveLocationGIF;
winrt::IDirect3DDevice g_RecordDevice{ nullptr };
std::shared_ptr<VideoRecordingSession> g_RecordingSession = nullptr;
std::shared_ptr<GifRecordingSession> g_GifRecordingSession = nullptr;
// Panorama capture globals
BOOL g_PanoramaCapturing = FALSE;
std::unique_ptr<PanoramaCapture> g_PanoramaCapture = nullptr;
BOOL g_PanoramaSaveToFile = FALSE;
type_pGetMonitorInfo pGetMonitorInfo;
type_MonitorFromPoint pMonitorFromPoint;
type_pSHAutoComplete pSHAutoComplete;
@@ -1995,6 +2004,8 @@ void UnregisterAllHotkeys( HWND hWnd )
UnregisterHotKey( hWnd, DEMOTYPE_RESET_HOTKEY );
UnregisterHotKey( hWnd, RECORD_GIF_HOTKEY );
UnregisterHotKey( hWnd, RECORD_GIF_WINDOW_HOTKEY );
UnregisterHotKey( hWnd, PANORAMA_HOTKEY );
UnregisterHotKey( hWnd, PANORAMA_SAVE_HOTKEY );
}
//----------------------------------------------------------------------------
@@ -2027,6 +2038,16 @@ void RegisterAllHotkeys(HWND hWnd)
// Register CTRL+8 for GIF recording and CTRL+ALT+8 for GIF window recording
RegisterHotKey(hWnd, RECORD_GIF_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, 568 && 0xFF);
RegisterHotKey(hWnd, RECORD_GIF_WINDOW_HOTKEY, MOD_CONTROL | MOD_ALT | MOD_NOREPEAT, 568 && 0xFF);
// Register panorama capture hotkeys
if (g_PanoramaToggleKey) {
OutputDebug( L"Registering PANORAMA_HOTKEY: key=0x%X, mod=0x%X\n", g_PanoramaToggleKey & 0xFF, g_PanoramaToggleMod );
BOOL reg1 = RegisterHotKey(hWnd, PANORAMA_HOTKEY, g_PanoramaToggleMod, g_PanoramaToggleKey & 0xFF);
BOOL reg2 = RegisterHotKey(hWnd, PANORAMA_SAVE_HOTKEY, (g_PanoramaToggleMod ^ MOD_SHIFT), g_PanoramaToggleKey & 0xFF);
OutputDebug( L"PANORAMA_HOTKEY registration result: %d, %d (LastError=%d)\n", reg1, reg2, GetLastError() );
} else {
OutputDebug( L"g_PanoramaToggleKey is 0, not registering panorama hotkey\n" );
}
}
@@ -3491,6 +3512,54 @@ inline auto CopyBytesFromTexture(winrt::com_ptr<ID3D11Texture2D> const& texture,
return bytes;
}
//----------------------------------------------------------------------------
//
// TextureToHBITMAP
//
// Convert ID3D11Texture2D to HBITMAP
//
//----------------------------------------------------------------------------
HBITMAP TextureToHBITMAP(winrt::com_ptr<ID3D11Texture2D> const& texture)
{
if (!texture)
{
return nullptr;
}
try
{
auto bytes = CopyBytesFromTexture(texture);
if (bytes.empty())
{
return nullptr;
}
D3D11_TEXTURE2D_DESC desc;
texture->GetDesc(&desc);
BITMAPINFO bitmapInfo = {};
bitmapInfo.bmiHeader.biSize = sizeof(bitmapInfo.bmiHeader);
bitmapInfo.bmiHeader.biWidth = desc.Width;
bitmapInfo.bmiHeader.biHeight = -static_cast<LONG>(desc.Height); // Top-down
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
void* bits = nullptr;
HBITMAP hBitmap = CreateDIBSection(nullptr, &bitmapInfo, DIB_RGB_COLORS, &bits, nullptr, 0);
if (hBitmap && bits)
{
CopyMemory(bits, bytes.data(), bytes.size());
}
return hBitmap;
}
catch (...)
{
return nullptr;
}
}
//----------------------------------------------------------------------------
//
@@ -3679,7 +3748,7 @@ winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndR
static std::filesystem::path lastSaveFolder;
wil::unique_cotaskmem_string chosenFolderPath;
wil::com_ptr<IShellItem> currentSelectedFolder;
bool bFolderChanged = false;
bool bFolderChanged = false;
if (SUCCEEDED(saveDialog->GetFolder(currentSelectedFolder.put())))
{
if (SUCCEEDED(currentSelectedFolder->GetDisplayName(SIGDN_FILESYSPATH, chosenFolderPath.put())))
@@ -4109,6 +4178,8 @@ LRESULT APIENTRY MainWndProc(
g_DemoTypeToggleMod = GetKeyMod( g_DemoTypeToggleKey );
g_SnipToggleMod = GetKeyMod( g_SnipToggleKey );
g_RecordToggleMod = GetKeyMod( g_RecordToggleKey );
g_PanoramaToggleMod = GetKeyMod( g_PanoramaToggleKey );
OutputDebug( L"Panorama hotkey settings: g_PanoramaToggleKey=0x%X, g_PanoramaToggleMod=0x%X\n", g_PanoramaToggleKey, g_PanoramaToggleMod );
if( !g_OptionsShown && !g_StartedByPowerToys ) {
// First run should show options when running as standalone. If not running as standalone,
@@ -4174,6 +4245,19 @@ LRESULT APIENTRY MainWndProc(
APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
// Register panorama hotkey at startup
if (g_PanoramaToggleKey) {
OutputDebug( L"Startup: Registering PANORAMA_HOTKEY: key=0x%X, mod=0x%X\n", g_PanoramaToggleKey & 0xFF, g_PanoramaToggleMod );
if (!RegisterHotKey(hWnd, PANORAMA_HOTKEY, g_PanoramaToggleMod, g_PanoramaToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, PANORAMA_SAVE_HOTKEY, (g_PanoramaToggleMod ^ MOD_SHIFT), g_PanoramaToggleKey & 0xFF)) {
OutputDebug( L"Startup: PANORAMA_HOTKEY registration FAILED, LastError=%d\n", GetLastError() );
MessageBox(hWnd, L"The specified panorama hotkey is already in use.\nSelect a different panorama hotkey.",
APPNAME, MB_ICONERROR);
showOptions = TRUE;
} else {
OutputDebug( L"Startup: PANORAMA_HOTKEY registration SUCCESS\n" );
}
}
if( showOptions ) {
SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 );
@@ -4188,6 +4272,7 @@ LRESULT APIENTRY MainWndProc(
return 0;
case WM_HOTKEY:
OutputDebug( L"WM_HOTKEY received: wParam=%d, lParam=0x%X\n", (int)wParam, (int)lParam );
if( g_RecordCropping == TRUE )
{
if( wParam != RECORD_CROP_HOTKEY )
@@ -4378,6 +4463,120 @@ LRESULT APIENTRY MainWndProc(
break;
}
case PANORAMA_SAVE_HOTKEY:
case PANORAMA_HOTKEY:
{
// Handle panorama capture hotkey
OutputDebug( L"PANORAMA_HOTKEY received, capturing=%d\n", g_PanoramaCapturing );
if( g_PanoramaCapturing )
{
// User pressed hotkey again or ESC - stop and process result
if( g_PanoramaCapture )
{
HBITMAP hResult = g_PanoramaCapture->Stop();
if( hResult )
{
if( g_PanoramaSaveToFile )
{
// Save to file - show Save As dialog
OPENFILENAME openFileName;
TCHAR filePath[MAX_PATH] = L"Panorama.png";
memset( &openFileName, 0, sizeof(openFileName) );
openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400;
openFileName.hwndOwner = hWnd;
openFileName.hInstance = static_cast<HINSTANCE>(g_hInstance);
openFileName.nMaxFile = sizeof(filePath)/sizeof(filePath[0]);
openFileName.Flags = OFN_LONGNAMES | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
openFileName.lpstrTitle = L"Save panorama screenshot...";
openFileName.lpstrDefExt = L"png";
openFileName.nFilterIndex = 1;
openFileName.lpstrFilter = L"PNG Image\0*.png\0\0";
openFileName.lpstrFile = filePath;
if( GetSaveFileName( &openFileName ) )
{
TCHAR targetFilePath[MAX_PATH];
_tcscpy( targetFilePath, filePath );
if( !_tcsrchr( targetFilePath, '.' ) )
{
_tcscat( targetFilePath, L".png" );
}
SavePng( targetFilePath, hResult );
}
}
else
{
// Copy to clipboard
if( OpenClipboard( hWnd ) )
{
EmptyClipboard();
SetClipboardData( CF_BITMAP, hResult );
CloseClipboard();
// Don't delete hResult - clipboard owns it now
hResult = nullptr;
}
}
if( hResult )
{
DeleteObject( hResult );
}
}
g_PanoramaCapture.reset();
}
g_PanoramaCapturing = FALSE;
g_PanoramaSaveToFile = FALSE;
}
else
{
// Block if LiveZoom/LiveDraw is active due to mirroring bug
if( IsWindowVisible( g_hWndLiveZoom )
&& ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
{
break;
}
// Start panorama capture
g_PanoramaSaveToFile = ( LOWORD( wParam ) == PANORAMA_SAVE_HOTKEY );
// Let user select the capture rectangle
SelectRectangle selectRectangle;
if( !selectRectangle.Start( hWnd ) )
{
break;
}
RECT captureRect = selectRectangle.SelectedRect();
selectRectangle.Stop();
// Validate rectangle size
if( (captureRect.right - captureRect.left) < 50 ||
(captureRect.bottom - captureRect.top) < 50 )
{
MessageBox( hWnd, L"Selected region is too small for panorama capture.", APPNAME, MB_OK | MB_ICONWARNING );
break;
}
// Create and start panorama capture
g_PanoramaCapture = std::make_unique<PanoramaCapture>();
if( g_PanoramaCapture->Start( hWnd, captureRect ) )
{
g_PanoramaCapturing = TRUE;
OutputDebug( L"Panorama capture started\n" );
}
else
{
g_PanoramaCapture.reset();
MessageBox( hWnd, L"Failed to start panorama capture.", APPNAME, MB_OK | MB_ICONERROR );
}
}
break;
}
case BREAK_HOTKEY:
//
// Go to break timer
@@ -5294,6 +5493,14 @@ LRESULT APIENTRY MainWndProc(
case WM_KEYDOWN:
// Handle ESC during panorama capture
if( g_PanoramaCapturing && wParam == VK_ESCAPE )
{
// Stop panorama capture and process result
PostMessage( hWnd, WM_HOTKEY, PANORAMA_HOTKEY, 0 );
return TRUE;
}
if( (g_TypeMode != TypeModeOff) && g_HaveTyped && static_cast<char>(wParam) != VK_UP && static_cast<char>(wParam) != VK_DOWN &&
(isprint( static_cast<char>(wParam)) ||
wParam == VK_RETURN || wParam == VK_DELETE || wParam == VK_BACK )) {
@@ -6384,11 +6591,11 @@ LRESULT APIENTRY MainWndProc(
{
// Reload the settings. This message is called from PowerToys after a setting is changed by the user.
reg.ReadRegSettings(RegSettings);
if (g_RecordingFormat == RecordingFormat::GIF)
{
g_RecordScaling = g_RecordScalingGIF;
g_RecordFrameRate = RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE;
g_RecordFrameRate = RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE;
}
else
{
@@ -6414,6 +6621,7 @@ LRESULT APIENTRY MainWndProc(
g_DemoTypeToggleMod = GetKeyMod(g_DemoTypeToggleKey);
g_SnipToggleMod = GetKeyMod(g_SnipToggleKey);
g_RecordToggleMod = GetKeyMod(g_RecordToggleKey);
g_PanoramaToggleMod = GetKeyMod(g_PanoramaToggleKey);
BOOL showOptions = FALSE;
if (g_ToggleKey)
{
@@ -6483,6 +6691,16 @@ LRESULT APIENTRY MainWndProc(
MessageBox(hWnd, L"The specified GIF recording hotkey is already in use.\nSelect a different GIF recording hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
// Register panorama hotkeys
if (g_PanoramaToggleKey)
{
if (!RegisterHotKey(hWnd, PANORAMA_HOTKEY, g_PanoramaToggleMod, g_PanoramaToggleKey & 0xFF) ||
!RegisterHotKey(hWnd, PANORAMA_SAVE_HOTKEY, (g_PanoramaToggleMod ^ MOD_SHIFT), g_PanoramaToggleKey & 0xFF))
{
MessageBox(hWnd, L"The specified panorama hotkey is already in use.\nSelect a different panorama hotkey.", APPNAME, MB_ICONERROR);
showOptions = TRUE;
}
}
if (showOptions)
{
// To open the PowerToys settings in the ZoomIt page.
@@ -6490,6 +6708,70 @@ LRESULT APIENTRY MainWndProc(
}
break;
}
case WM_USER_PANORAMA_STOP:
{
OutputDebug( L"WM_USER_PANORAMA_STOP received\n" );
if( g_PanoramaCapturing && g_PanoramaCapture )
{
HBITMAP hResult = g_PanoramaCapture->Stop();
if( hResult )
{
if( g_PanoramaSaveToFile )
{
// Save to file - show Save As dialog
OPENFILENAME openFileName;
TCHAR filePath[MAX_PATH] = L"Panorama.png";
memset( &openFileName, 0, sizeof(openFileName) );
openFileName.lStructSize = OPENFILENAME_SIZE_VERSION_400;
openFileName.hwndOwner = hWnd;
openFileName.hInstance = static_cast<HINSTANCE>(g_hInstance);
openFileName.nMaxFile = sizeof(filePath)/sizeof(filePath[0]);
openFileName.Flags = OFN_LONGNAMES | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
openFileName.lpstrTitle = L"Save panorama screenshot...";
openFileName.lpstrDefExt = L"png";
openFileName.nFilterIndex = 1;
openFileName.lpstrFilter = L"PNG Image\0*.png\0\0";
openFileName.lpstrFile = filePath;
if( GetSaveFileName( &openFileName ) )
{
TCHAR targetFilePath[MAX_PATH];
_tcscpy( targetFilePath, filePath );
if( !_tcsrchr( targetFilePath, '.' ) )
{
_tcscat( targetFilePath, L".png" );
}
SavePng( targetFilePath, hResult );
}
DeleteObject( hResult );
}
else
{
// Copy to clipboard
if( OpenClipboard( hWnd ) )
{
EmptyClipboard();
SetClipboardData( CF_BITMAP, hResult );
CloseClipboard();
// Don't delete hResult - clipboard owns it now
}
else
{
DeleteObject( hResult );
}
}
}
g_PanoramaCapture.reset();
g_PanoramaCapturing = FALSE;
g_PanoramaSaveToFile = FALSE;
}
break;
}
case WM_COMMAND:
switch(LOWORD( wParam )) {
@@ -7081,6 +7363,14 @@ LRESULT APIENTRY MainWndProc(
case WM_DESTROY:
// Clean up panorama capture if active
if( g_PanoramaCapturing && g_PanoramaCapture )
{
g_PanoramaCapture->Cancel();
g_PanoramaCapture.reset();
g_PanoramaCapturing = FALSE;
}
PostQuitMessage( 0 );
break;

View File

@@ -60,6 +60,7 @@
#include "SelectRectangle.h"
#include "DemoType.h"
#include "versionhelper.h"
#include "PanoramaCapture.h"
// WIL
#include <wil/com.h>
@@ -83,6 +84,9 @@
#include <regex>
#include <fstream>
#include <sstream>
#include <thread>
#include <mutex>
#include <chrono>
// robmikh.common
#include <robmikh.common/composition.interop.h>

View File

@@ -104,13 +104,16 @@
#define IDC_COPY_CROP 40008
#define IDC_SAVE_CROP 40009
#define IDC_DEMOTYPE_HOTKEY 40011
#define IDC_PANORAMA_HOTKEY 40013
#define IDC_PANORAMA_CROP 40014
#define IDC_PANORAMA_SAVE 40015
// Next default values for new objects
//
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 118
#define _APS_NEXT_COMMAND_VALUE 40013
#define _APS_NEXT_COMMAND_VALUE 40016
#define _APS_NEXT_CONTROL_VALUE 1078
#define _APS_NEXT_SYMED_VALUE 101
#endif