Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 6 additions & 123 deletions Ink Canvas/MainWindow_cs/MW_ImageInsert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Ink;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
Expand All @@ -31,17 +30,15 @@ public struct ScreenshotResult
public Bitmap CameraImage;
public BitmapSource CameraBitmapSource;
public bool AddToWhiteboard;
public bool IncludeInk;

public ScreenshotResult(Rectangle area, List<Point> path = null, Bitmap cameraImage = null,
BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false, bool includeInk = true)
BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false)
{
Area = area;
Path = path;
CameraImage = cameraImage;
CameraBitmapSource = cameraBitmapSource;
AddToWhiteboard = addToWhiteboard;
IncludeInk = includeInk;
}
}

Expand Down Expand Up @@ -98,7 +95,7 @@ private async Task CaptureScreenshotAndInsert()
else if (screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0)
{
// 屏幕截图
using (var originalBitmap = CaptureScreenAreaWithOptionalInk(screenshotResult.Value.Area, screenshotResult.Value.IncludeInk))
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Hide window during area capture for insert flow

In CaptureScreenshotAndInsert, the capture call was switched to CaptureScreenArea(...), but this method no longer hides the main window before grabbing pixels. Because the window is restored immediately after selector close (before this call), the app chrome/toolbar/ink overlay can be captured into the inserted screenshot when it overlaps the selected region. This regresses the documented behavior of avoiding self-capture in the insert workflow.

Useful? React with 👍 / 👎.

{
if (originalBitmap != null)
{
Expand Down Expand Up @@ -210,16 +207,7 @@ private async Task CaptureFullScreenAndInsert()
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
var selectorWindow = new ScreenshotSelectorWindow(shouldIncludeInk =>
{
if (inkCanvas == null)
{
return;
}

inkCanvas.Visibility = shouldIncludeInk ? Visibility.Visible : Visibility.Collapsed;
Dispatcher.Invoke(() => { }, DispatcherPriority.Render);
});
var selectorWindow = new ScreenshotSelectorWindow();
if (selectorWindow.ShowDialog() == true)
{
// 检查是否是摄像头截图
Expand All @@ -230,8 +218,7 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
null, // 摄像头截图不需要路径
null, // 不再使用Bitmap
selectorWindow.CameraBitmapSource, // 摄像头BitmapSource
selectorWindow.ShouldAddToWhiteboard,
selectorWindow.ShouldIncludeInk
selectorWindow.ShouldAddToWhiteboard
);
}
else if (selectorWindow.CameraImage != null)
Expand All @@ -241,8 +228,7 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
null, // 摄像头截图不需要路径
selectorWindow.CameraImage, // 摄像头图像
null,
selectorWindow.ShouldAddToWhiteboard,
selectorWindow.ShouldIncludeInk
selectorWindow.ShouldAddToWhiteboard
);
}
else
Expand All @@ -252,8 +238,7 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
selectorWindow.SelectedPath,
null,
null,
selectorWindow.ShouldAddToWhiteboard,
selectorWindow.ShouldIncludeInk
selectorWindow.ShouldAddToWhiteboard
);
}
}
Expand Down Expand Up @@ -319,108 +304,6 @@ private Bitmap CaptureScreenArea(Rectangle area)
}
}

private Bitmap CaptureScreenAreaWithOptionalInk(Rectangle area, bool includeInk)
{
Bitmap bitmap = null;
StrokeCollection strokesForOverlay = null;
Point? inkCanvasTopLeftOnScreen = null;
System.Windows.Media.Matrix? dpiTransform = null;
var originalWindowVisibility = Visibility;

try
{
if (includeInk && inkCanvas != null && inkCanvas.Strokes.Count > 0)
{
strokesForOverlay = inkCanvas.Strokes.Clone();

var source = PresentationSource.FromVisual(inkCanvas);
if (source?.CompositionTarget != null)
{
dpiTransform = source.CompositionTarget.TransformToDevice;
}

inkCanvasTopLeftOnScreen = inkCanvas.PointToScreen(new Point(0, 0));
}

// 先隐藏主窗口再截取屏幕,确保基础截图不包含主线程 UI(含墨迹层)。
Visibility = Visibility.Hidden;
Dispatcher.Invoke(() => { }, DispatcherPriority.Render);

bitmap = CaptureScreenArea(area);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"准备截图时处理墨迹失败: {ex.Message}", LogHelper.LogType.Error);
bitmap?.Dispose();
return null;
}
finally
{
Visibility = originalWindowVisibility;
Dispatcher.Invoke(() => { }, DispatcherPriority.Render);
}

if (bitmap == null || !includeInk || strokesForOverlay == null || strokesForOverlay.Count == 0)
{
return bitmap;
}

try
{
OverlayInkStrokesOnBitmap(bitmap, area, strokesForOverlay, inkCanvasTopLeftOnScreen, dpiTransform);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"叠加墨迹到截图失败: {ex.Message}", LogHelper.LogType.Error);
}

return bitmap;
}

private void OverlayInkStrokesOnBitmap(
Bitmap bitmap,
Rectangle area,
StrokeCollection strokes,
Point? inkCanvasTopLeftOnScreen = null,
System.Windows.Media.Matrix? dpiTransform = null)
{
if (bitmap == null || strokes == null || strokes.Count == 0)
{
return;
}

var transform = dpiTransform ?? new System.Windows.Media.Matrix(1, 0, 0, 1, 0, 0);
var topLeft = inkCanvasTopLeftOnScreen ?? new Point(area.X, area.Y);
var offsetX = topLeft.X * transform.M11 - area.X;
var offsetY = topLeft.Y * transform.M22 - area.Y;

var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
drawingContext.PushTransform(new TranslateTransform(offsetX, offsetY));
strokes.Draw(drawingContext);
drawingContext.Pop();
}

var renderBitmap = new RenderTargetBitmap(bitmap.Width, bitmap.Height, 96, 96, PixelFormats.Pbgra32);
renderBitmap.Render(drawingVisual);

var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));

using (var memoryStream = new MemoryStream())
{
encoder.Save(memoryStream);
memoryStream.Position = 0;
using (var overlayBitmap = new Bitmap(memoryStream))
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.CompositingMode = CompositingMode.SourceOver;
graphics.DrawImage(overlayBitmap, 0, 0, bitmap.Width, bitmap.Height);
}
}
}

/// <summary>
/// 将截图插入到画布
/// </summary>
Expand Down
120 changes: 11 additions & 109 deletions Ink Canvas/MainWindow_cs/MW_Screenshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ namespace Ink_Canvas
{
public partial class MainWindow : Window
{
private bool _isAreaScreenshotInProgress;

/// <summary>
/// 在切页/加页场景下使用:先捕获当前画面到内存并克隆墨迹,然后立即返回;截图与墨迹保存在后台异步执行,不阻塞切页。
/// 调用方应在调用本方法后立即执行 SaveStrokes、ClearStrokes、切页、RestoreStrokes 等逻辑。
Expand Down Expand Up @@ -159,98 +157,13 @@ internal void SaveScreenShotToDesktop()
SaveInkCanvasStrokes(false);
}

private struct AnnotationSuspendState
{
public bool WasInAnnotationMode;
public int OriginalMode;
public System.Windows.Media.Brush OriginalFakeBackground;
public double OriginalFakeBackgroundOpacity;
public Visibility OriginalBackgroundCoverHolderVisibility;
public Visibility OriginalCanvasControlsVisibility;
public object OriginalHideInkCanvasContent;
}

private AnnotationSuspendState SuspendAnnotationForAreaScreenshotIfNeeded()
{
var state = new AnnotationSuspendState
{
WasInAnnotationMode = GridTransparencyFakeBackground.Background != System.Windows.Media.Brushes.Transparent,
OriginalMode = currentMode,
OriginalFakeBackground = GridTransparencyFakeBackground.Background,
OriginalFakeBackgroundOpacity = GridTransparencyFakeBackground.Opacity,
OriginalBackgroundCoverHolderVisibility = GridBackgroundCoverHolder.Visibility,
OriginalCanvasControlsVisibility = StackPanelCanvasControls.Visibility,
OriginalHideInkCanvasContent = BtnHideInkCanvas.Content
};

if (!state.WasInAnnotationMode)
{
return state;
}

// 仅暂停批注视觉态,避免调用 BtnHideInkCanvas_Click 触发自动截图/上传及白板状态读写。
GridTransparencyFakeBackground.Opacity = 0;
GridTransparencyFakeBackground.Background = System.Windows.Media.Brushes.Transparent;
GridBackgroundCoverHolder.Visibility = Visibility.Collapsed;
StackPanelCanvasControls.Visibility = Visibility.Collapsed;
CheckEnableTwoFingerGestureBtnVisibility(false);
HideSubPanels("cursor");
BtnHideInkCanvas.Content = "显示\n画板";

return state;
}

private void RestoreAnnotationAfterAreaScreenshot(AnnotationSuspendState state)
{
if (!state.WasInAnnotationMode || currentMode != state.OriginalMode)
{
return;
}

GridTransparencyFakeBackground.Opacity = state.OriginalFakeBackgroundOpacity;
GridTransparencyFakeBackground.Background = state.OriginalFakeBackground;
GridBackgroundCoverHolder.Visibility = state.OriginalBackgroundCoverHolderVisibility;
StackPanelCanvasControls.Visibility = state.OriginalCanvasControlsVisibility;
CheckEnableTwoFingerGestureBtnVisibility(state.OriginalCanvasControlsVisibility == Visibility.Visible);
BtnHideInkCanvas.Content = state.OriginalHideInkCanvasContent;
}

internal async Task SaveAreaScreenShotToDesktop()
{
if (_isAreaScreenshotInProgress)
{
ShowNotification("截图进行中,请先完成当前截图");
return;
}

_isAreaScreenshotInProgress = true;

var annotationState = SuspendAnnotationForAreaScreenshotIfNeeded();
var originalFloatingBarVisibility = ViewboxFloatingBar.Visibility;
var shouldRestoreFloatingBarVisibility = true;
var originalVisibility = Visibility;
try
{
if (annotationState.WasInAnnotationMode)
{
// 等待一次 UI 刷新,确保批注暂停状态已完成。
await System.Windows.Threading.Dispatcher.Yield(System.Windows.Threading.DispatcherPriority.Render);
}

// 从浮动栏触发选区截图时,临时隐藏浮动栏,避免遮挡选区与误入截图。
if (originalFloatingBarVisibility == Visibility.Visible)
{
ViewboxFloatingBar.Visibility = Visibility.Collapsed;
await System.Windows.Threading.Dispatcher.Yield(System.Windows.Threading.DispatcherPriority.Render);
}

// 选区截图时确保墨迹层可见,避免从浮动栏触发时出现“先隐藏再截图”。
if (inkCanvas.Visibility != Visibility.Visible)
{
inkCanvas.Visibility = Visibility.Visible;
}

// 等待一次 UI 刷新,确保可见性状态已生效。
await System.Windows.Threading.Dispatcher.Yield(System.Windows.Threading.DispatcherPriority.Render);
Visibility = Visibility.Hidden;
await Task.Delay(200);

var screenshotResult = await ShowScreenshotSelector();

Expand All @@ -262,12 +175,7 @@ internal async Task SaveAreaScreenShotToDesktop()

if (screenshotResult.Value.AddToWhiteboard)
{
// 仅在白板接管流程已确认完成时,才跳过本方法对浮动栏可见性的恢复。
var whiteboardHandoffCompleted = await AddScreenshotToNewWhiteboardPage(screenshotResult.Value);
if (whiteboardHandoffCompleted)
{
shouldRestoreFloatingBarVisibility = false;
}
await AddScreenshotToNewWhiteboardPage(screenshotResult.Value);
return;
}

Expand All @@ -281,7 +189,7 @@ internal async Task SaveAreaScreenShotToDesktop()
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
$"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png");

using (var originalBitmap = CaptureScreenAreaWithOptionalInk(screenshotResult.Value.Area, screenshotResult.Value.IncludeInk))
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
{
if (originalBitmap == null)
{
Expand Down Expand Up @@ -327,16 +235,11 @@ internal async Task SaveAreaScreenShotToDesktop()
}
finally
{
_isAreaScreenshotInProgress = false;
if (shouldRestoreFloatingBarVisibility)
{
ViewboxFloatingBar.Visibility = originalFloatingBarVisibility;
}
RestoreAnnotationAfterAreaScreenshot(annotationState);
Visibility = originalVisibility;
}
}

private async Task<bool> AddScreenshotToNewWhiteboardPage(ScreenshotResult screenshotResult)
private async Task AddScreenshotToNewWhiteboardPage(ScreenshotResult screenshotResult)
{
// 先在当前场景准备截图数据,再进白板,避免误截到白板页面
BitmapSource bitmapSourceForClipboard = null;
Expand All @@ -356,15 +259,15 @@ private async Task<bool> AddScreenshotToNewWhiteboardPage(ScreenshotResult scree
if (screenshotResult.Area.Width <= 0 || screenshotResult.Area.Height <= 0)
{
ShowNotification("未选择有效截图区域");
return false;
return;
}

using (var originalBitmap = CaptureScreenAreaWithOptionalInk(screenshotResult.Area, screenshotResult.IncludeInk))
using (var originalBitmap = CaptureScreenArea(screenshotResult.Area))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent self-capture in add-to-whiteboard path

AddScreenshotToNewWhiteboardPage now captures with CaptureScreenArea(...) directly, which assumes callers already hid the main window. That assumption is false for the CaptureScreenshotAndInsert caller (it restores visibility before dispatching to this method), so choosing “添加到白板” from the selector can include the app UI in the captured image. The capture step should hide the window (or use a helper that does) to keep behavior consistent across callers.

Useful? React with 👍 / 👎.

{
if (originalBitmap == null)
{
ShowNotification("截图失败");
return false;
return;
}

Bitmap finalBitmap = originalBitmap;
Expand Down Expand Up @@ -393,7 +296,7 @@ private async Task<bool> AddScreenshotToNewWhiteboardPage(ScreenshotResult scree
if (bitmapSourceForClipboard == null)
{
ShowNotification("截图转换失败");
return false;
return;
}

// 图像已拷贝到内存后再进入白板
Expand All @@ -408,7 +311,6 @@ private async Task<bool> AddScreenshotToNewWhiteboardPage(ScreenshotResult scree
BtnWhiteBoardAdd_Click(null, EventArgs.Empty);

await InsertBitmapSourceToCanvas(bitmapSourceForClipboard);
return true;
}

/// <summary>
Expand Down
11 changes: 0 additions & 11 deletions Ink Canvas/Windows/ScreenshotSelectorWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,6 @@
<Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}"
Margin="8,0"
Background="#404040" />

<!-- 选项开关 -->
<CheckBox Name="IncludeInkCheckBox"
Content="包含墨迹"
IsChecked="True"
Margin="8,0"
VerticalAlignment="Center"
Foreground="White"
FontWeight="Medium"
Checked="IncludeInkCheckBox_Checked"
Unchecked="IncludeInkCheckBox_Unchecked" />

<!-- 操作按钮 -->
<Button Name="ConfirmButton"
Expand Down
Loading
Loading