mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-18 18:17:43 +01:00
Compare commits
1 Commits
shawn/impr
...
marioh/pan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2024b269a7 |
1032
src/modules/ZoomIt/ZoomIt/PanoramaCapture.cpp
Normal file
1032
src/modules/ZoomIt/ZoomIt/PanoramaCapture.cpp
Normal file
File diff suppressed because it is too large
Load Diff
169
src/modules/ZoomIt/ZoomIt/PanoramaCapture.h
Normal file
169
src/modules/ZoomIt/ZoomIt/PanoramaCapture.h
Normal 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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) },
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user