Merge pull request #443 from InkCanvasForClass/beta

合并net6
This commit is contained in:
CJK_mkp
2026-04-12 17:23:23 +08:00
committed by GitHub
9 changed files with 454 additions and 84 deletions
+8 -4
View File
@@ -1,4 +1,5 @@
{
"$schema": "https://www.schemastore.org/all-contributors.json",
"projectName": "community",
"projectOwner": "InkCanvasForClass",
"files": [
@@ -6,7 +7,7 @@
],
"commitType": "docs",
"commitConvention": "angular",
"contributorsPerLine": 7,
"contributorsPerLine": 5,
"contributors": [
{
"login": "CJKmkp",
@@ -100,7 +101,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/156585442?v=4",
"profile": "https://github.com/Tayasui-rainnya",
"contributions": [
"design"
"design",
"code"
]
},
{
@@ -110,7 +112,8 @@
"profile": "https://github.com/doudou0720",
"contributions": [
"code",
"blog"
"blog",
"infra"
]
},
{
@@ -141,5 +144,6 @@
"blog"
]
}
]
],
"repoType": "github"
}
+18 -47
View File
@@ -554,37 +554,22 @@ namespace Ink_Canvas
BoardHighlightPenTabButton.Background = new SolidColorBrush(Colors.Transparent);
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Collapsed;
// PenPalette.Margin = new Thickness(-160, -200, -33, 32);
// 动态计算面板位置,使其对齐笔按钮(考虑快捷调色盘等动态宽度)
await Dispatcher.InvokeAsync(() =>
{
var marginAnimation = new ThicknessAnimation
{
Duration = TimeSpan.FromSeconds(0.1),
From = PenPalette.Margin,
To = new Thickness(-160, -200, -33, 32),
EasingFunction = new CubicEase()
};
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
PenPalette.BeginAnimation(MarginProperty, null);
var currentMargin = PenPalette.Margin;
// 先设置正确的Top/Bottom,保持当前Left/Right
PenPalette.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 32);
UpdatePenPalettePosition();
});
await Dispatcher.InvokeAsync(() =>
{
var marginAnimation = new ThicknessAnimation
{
Duration = TimeSpan.FromSeconds(0.1),
From = PenPalette.Margin,
To = new Thickness(-160, -200, -33, 50),
EasingFunction = new CubicEase()
};
BoardPenPaletteGrid.BeginAnimation(MarginProperty, marginAnimation);
BoardPenPaletteGrid.BeginAnimation(MarginProperty, null);
var currentMargin = BoardPenPaletteGrid.Margin;
BoardPenPaletteGrid.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 50);
});
await Task.Delay(100);
await Dispatcher.InvokeAsync(() => { PenPalette.Margin = new Thickness(-160, -200, -33, 32); });
await Dispatcher.InvokeAsync(() => { BoardPenPaletteGrid.Margin = new Thickness(-160, -200, -33, 50); });
}
else if (penType == 1)
{
@@ -622,36 +607,22 @@ namespace Ink_Canvas
BoardHighlightPenTabButton.Background = new SolidColorBrush(Color.FromArgb(72, 219, 234, 254));
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Visible;
// PenPalette.Margin = new Thickness(-160, -157, -33, 32);
// 动态计算面板位置,使其对齐笔按钮(考虑快捷调色盘等动态宽度)
await Dispatcher.InvokeAsync(() =>
{
var marginAnimation = new ThicknessAnimation
{
Duration = TimeSpan.FromSeconds(0.1),
From = PenPalette.Margin,
To = new Thickness(-160, -157, -33, 32),
EasingFunction = new CubicEase()
};
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
PenPalette.BeginAnimation(MarginProperty, null);
var currentMargin = PenPalette.Margin;
// 荧光笔模式面板稍小,使用不同的Top/Bottom
PenPalette.Margin = new Thickness(currentMargin.Left, -157, currentMargin.Right, 32);
UpdatePenPalettePosition();
});
await Dispatcher.InvokeAsync(() =>
{
var marginAnimation = new ThicknessAnimation
{
Duration = TimeSpan.FromSeconds(0.1),
From = PenPalette.Margin,
To = new Thickness(-160, -154, -33, 50),
EasingFunction = new CubicEase()
};
BoardPenPaletteGrid.BeginAnimation(MarginProperty, marginAnimation);
BoardPenPaletteGrid.BeginAnimation(MarginProperty, null);
var currentMargin = BoardPenPaletteGrid.Margin;
BoardPenPaletteGrid.Margin = new Thickness(currentMargin.Left, -154, currentMargin.Right, 50);
});
await Task.Delay(100);
await Dispatcher.InvokeAsync(() => { PenPalette.Margin = new Thickness(-160, -157, -33, 32); });
await Dispatcher.InvokeAsync(() => { BoardPenPaletteGrid.Margin = new Thickness(-160, -154, -33, 50); });
}
}
@@ -49,6 +49,7 @@ namespace Ink_Canvas
else
{
HideSubPanels();
UpdateTwoFingerGestureBorderPosition();
AnimationsHelper.ShowWithSlideFromBottomAndFade(TwoFingerGestureBorder);
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardTwoFingerGestureBorder);
}
@@ -1712,6 +1713,7 @@ namespace Ink_Canvas
else
{
HideSubPanels();
UpdateBorderToolsPosition();
AnimationsHelper.ShowWithSlideFromBottomAndFade(BorderTools);
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderTools);
}
@@ -2401,6 +2403,7 @@ namespace Ink_Canvas
else
{
HideSubPanels();
UpdatePenPalettePosition();
AnimationsHelper.ShowWithSlideFromBottomAndFade(PenPalette);
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardPenPalette);
}
@@ -2510,6 +2513,7 @@ namespace Ink_Canvas
// 已是橡皮状态,再次点击才弹出/收起面板
if (EraserSizePanel.Visibility == Visibility.Collapsed)
{
UpdateEraserSizePanelPosition();
AnimationsHelper.ShowWithSlideFromBottomAndFade(EraserSizePanel);
if (BoardEraserSizePanel != null)
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardEraserSizePanel);
@@ -2560,6 +2564,7 @@ namespace Ink_Canvas
// 已是橡皮状态,再次点击才弹出/收起面板
if (BoardEraserSizePanel != null && BoardEraserSizePanel.Visibility == Visibility.Collapsed)
{
UpdateEraserSizePanelPosition();
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardEraserSizePanel);
AnimationsHelper.ShowWithSlideFromBottomAndFade(EraserSizePanel);
}
@@ -4161,6 +4166,101 @@ namespace Ink_Canvas
}
}
/// <summary>
/// 通用子面板位置更新方法:根据触发按钮的位置,动态调整子面板的水平位置,
/// 使面板水平中心对齐按钮中心。不改变面板大小,不改变上下边距。
/// </summary>
/// <param name="button">触发按钮元素</param>
/// <param name="panel">需要定位的子面板</param>
/// <param name="defaultPanelWidth">面板默认宽度(当无法从Margin计算时使用)</param>
private void UpdateSubPanelPosition(FrameworkElement button, FrameworkElement panel, double defaultPanelWidth)
{
try
{
if (button == null || panel == null) return;
if (!(panel.Parent is FrameworkElement panelContainer)) return;
var ancestor = StackPanelFloatingBar;
if (ancestor == null) return;
// 获取按钮中心的X坐标(相对于 StackPanelFloatingBar 坐标系)
var buttonTransform = button.TransformToAncestor(ancestor);
var buttonOrigin = buttonTransform.Transform(new Point(0, 0));
double buttonCenterX = buttonOrigin.X + button.ActualWidth / 2.0;
// 获取面板容器(零宽度Grid)的X坐标(相对于 StackPanelFloatingBar 坐标系)
var containerTransform = panelContainer.TransformToAncestor(ancestor);
var containerOrigin = containerTransform.Transform(new Point(0, 0));
double containerX = containerOrigin.X;
// 计算当前面板宽度(保持不变):panelWidth = -Margin.Left - Margin.Right
double currentLeft = panel.Margin.Left;
double currentRight = panel.Margin.Right;
double panelWidth = -currentLeft - currentRight;
if (panelWidth <= 0) panelWidth = defaultPanelWidth;
// 计算新的左边距,使面板水平中心对齐按钮:
// panel_center = containerX + newLeft + panelWidth/2 = buttonCenterX
// => newLeft = buttonCenterX - containerX - panelWidth/2
double newLeft = buttonCenterX - containerX - panelWidth / 2.0;
// 保持面板宽度不变:-newLeft - newRight = panelWidth
// => newRight = -panelWidth - newLeft
double newRight = -panelWidth - newLeft;
// 清除可能残留的 Margin 动画(HoldEnd 会阻止本地值生效)
panel.BeginAnimation(FrameworkElement.MarginProperty, null);
// 更新边距,仅调整Left/Right,保持Top/Bottom不变
var margin = panel.Margin;
panel.Margin = new Thickness(newLeft, margin.Top, newRight, margin.Bottom);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"更新子面板位置失败: {ex.Message}", LogHelper.LogType.Error);
}
}
/// <summary>
/// 更新批注子面板(PenPalette)的弹出位置,使其水平中心对齐笔按钮。
/// </summary>
private void UpdatePenPalettePosition()
{
UpdateSubPanelPosition(Pen_Icon, PenPalette, 193);
}
/// <summary>
/// 更新工具面板(BorderTools)的弹出位置,使其水平中心对齐工具按钮。
/// </summary>
private void UpdateBorderToolsPosition()
{
UpdateSubPanelPosition(ToolsFloatingBarBtn, BorderTools, 119);
}
/// <summary>
/// 更新橡皮擦尺寸面板(EraserSizePanel)的弹出位置,使其水平中心对齐橡皮擦按钮。
/// </summary>
private void UpdateEraserSizePanelPosition()
{
UpdateSubPanelPosition(Eraser_Icon, EraserSizePanel, 120);
}
/// <summary>
/// 更新形状绘制面板(BorderDrawShape)的弹出位置,使其水平中心对齐形状按钮。
/// </summary>
private void UpdateBorderDrawShapePosition()
{
UpdateSubPanelPosition(ShapeDrawFloatingBarBtn, BorderDrawShape, 317);
}
/// <summary>
/// 更新手势面板(TwoFingerGestureBorder)的弹出位置,使其水平中心对齐手势按钮。
/// </summary>
private void UpdateTwoFingerGestureBorderPosition()
{
UpdateSubPanelPosition(EnableTwoFingerGestureBorder, TwoFingerGestureBorder, 119);
}
/// <summary>
/// 隐藏浮动栏高光显示
/// </summary>
+187 -14
View File
@@ -30,15 +30,19 @@ namespace Ink_Canvas
public Bitmap CameraImage;
public BitmapSource CameraBitmapSource;
public bool AddToWhiteboard;
public bool IncludeInk;
public BitmapSource InkOverlayBitmapSource;
public ScreenshotResult(Rectangle area, List<Point> path = null, Bitmap cameraImage = null,
BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false)
BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false, bool includeInk = false, BitmapSource inkOverlayBitmapSource = null)
{
Area = area;
Path = path;
CameraImage = cameraImage;
CameraBitmapSource = cameraBitmapSource;
AddToWhiteboard = addToWhiteboard;
IncludeInk = includeInk;
InkOverlayBitmapSource = inkOverlayBitmapSource;
}
}
@@ -60,6 +64,8 @@ namespace Ink_Canvas
{
try
{
var inkOverlayPreview = CreateInkOverlayPreviewBitmapSource();
// 隐藏主窗口以避免截图包含窗口本身
var originalVisibility = Visibility;
Visibility = Visibility.Hidden;
@@ -68,7 +74,7 @@ namespace Ink_Canvas
await Task.Delay(200);
// 启动区域选择截图
var screenshotResult = await ShowScreenshotSelector();
var screenshotResult = await ShowScreenshotSelector(inkOverlayPreview);
// 恢复窗口显示
Visibility = originalVisibility;
@@ -104,11 +110,33 @@ namespace Ink_Canvas
try
{
if (screenshotResult.Value.IncludeInk && screenshotResult.Value.InkOverlayBitmapSource != null)
{
var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Value.Area, screenshotResult.Value.InkOverlayBitmapSource);
if (withInkBitmap != null && withInkBitmap != finalBitmap)
{
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
{
finalBitmap.Dispose();
}
finalBitmap = withInkBitmap;
needDisposeFinalBitmap = true;
}
}
// 如果有路径信息,应用形状遮罩
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
{
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
if (maskedBitmap != null && maskedBitmap != finalBitmap)
{
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
{
finalBitmap.Dispose();
}
finalBitmap = maskedBitmap;
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
}
}
// 将截图转换为WPF Image并插入到画布
@@ -190,16 +218,19 @@ namespace Ink_Canvas
}
/// <summary>
/// 显示截图区域选择器
/// 显示截图区域选择器并返回用户的截图结果(区域截图或摄像头截图)。
/// </summary>
/// <returns>截图结果,包含区域、路径和摄像头截图信息</returns>
/// <param name="inkOverlayPreview">当用户选择包含墨迹的区域截图时,用于作为墨迹叠加的预览 <see cref="BitmapSource"/>;可为 <c>null</c>。</param>
/// <returns>若用户确认截图则返回 <see cref="ScreenshotResult"/>,否则返回 <c>null</c>。返回的结果可能为摄像头截图或区域截图,摄像头截图会包含 <see cref="ScreenshotResult.CameraBitmapSource"/> 或 <see cref="ScreenshotResult.CameraImage"/>,区域截图会包含有效的区域与路径。</returns>
/// <remarks>
/// 该方法会:
/// 1. 显示截图选择器窗口
/// 2. 获取用户选择的区域或摄像头截图
/// 3. 返回截图结果
/// 1. 在 UI 线程(通过 <see cref="Application.Current.Dispatcher"/>)上显示截图选择器窗口 <see cref="ScreenshotSelectorWindow"/>
/// 2. 获取用户选择的区域截图或摄像头截图
/// 3. 根据用户选择构建并返回 <see cref="ScreenshotResult"/>
/// 4. 若用户取消对话框或未确认截图,返回 <c>null</c>
/// 5. 方法内部捕获异常并记录日志(不会向调用方抛出异常),如需外部处理请调整实现以重新抛出或传回错误信息。
/// </remarks>
private async Task<ScreenshotResult?> ShowScreenshotSelector()
private async Task<ScreenshotResult?> ShowScreenshotSelector(BitmapSource inkOverlayPreview = null)
{
ScreenshotResult? result = null;
@@ -207,7 +238,7 @@ namespace Ink_Canvas
{
await Application.Current.Dispatcher.InvokeAsync(() =>
{
var selectorWindow = new ScreenshotSelectorWindow();
var selectorWindow = new ScreenshotSelectorWindow(inkOverlayPreview);
if (selectorWindow.ShowDialog() == true)
{
// 检查是否是摄像头截图
@@ -218,7 +249,9 @@ namespace Ink_Canvas
null, // 摄像头截图不需要路径
null, // 不再使用Bitmap
selectorWindow.CameraBitmapSource, // 摄像头BitmapSource
selectorWindow.ShouldAddToWhiteboard
selectorWindow.ShouldAddToWhiteboard,
false,
null
);
}
else if (selectorWindow.CameraImage != null)
@@ -228,7 +261,9 @@ namespace Ink_Canvas
null, // 摄像头截图不需要路径
selectorWindow.CameraImage, // 摄像头图像
null,
selectorWindow.ShouldAddToWhiteboard
selectorWindow.ShouldAddToWhiteboard,
false,
null
);
}
else
@@ -238,7 +273,9 @@ namespace Ink_Canvas
selectorWindow.SelectedPath,
null,
null,
selectorWindow.ShouldAddToWhiteboard
selectorWindow.ShouldAddToWhiteboard,
selectorWindow.IncludeInkInScreenshot,
selectorWindow.IncludeInkInScreenshot ? inkOverlayPreview : null
);
}
}
@@ -304,6 +341,142 @@ namespace Ink_Canvas
}
}
private BitmapSource CreateInkOverlayPreviewBitmapSource()
{
try
{
if (inkCanvas == null)
{
return null;
}
if ((inkCanvas.Strokes?.Count ?? 0) == 0 && inkCanvas.Children.Count == 0)
{
return null;
}
if (inkCanvas.ActualWidth <= 0 || inkCanvas.ActualHeight <= 0)
{
return null;
}
var virtualScreen = SystemInformation.VirtualScreen;
var source = PresentationSource.FromVisual(inkCanvas);
var transformToDevice = source?.CompositionTarget?.TransformToDevice ?? System.Windows.Media.Matrix.Identity;
// PointToScreen 返回WPF坐标(DIP),统一转换为设备像素后再与 VirtualScreen 对齐
var inkTopLeftDip = inkCanvas.PointToScreen(new Point(0, 0));
var inkTopLeftPx = transformToDevice.Transform(inkTopLeftDip);
var offsetX = inkTopLeftPx.X - virtualScreen.Left;
var offsetY = inkTopLeftPx.Y - virtualScreen.Top;
var widthPx = inkCanvas.ActualWidth * transformToDevice.M11;
var heightPx = inkCanvas.ActualHeight * transformToDevice.M22;
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
// 使用完整 InkCanvas 视觉树,确保包含图片等子元素
var visualBrush = new VisualBrush(inkCanvas)
{
Stretch = Stretch.Fill
};
dc.DrawRectangle(visualBrush, null, new Rect(offsetX, offsetY, widthPx, heightPx));
}
var rtb = new RenderTargetBitmap(
Math.Max(1, virtualScreen.Width),
Math.Max(1, virtualScreen.Height),
96,
96,
PixelFormats.Pbgra32);
rtb.Render(drawingVisual);
rtb.Freeze();
return rtb;
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"创建截图墨迹预览失败: {ex.Message}", LogHelper.LogType.Warning);
return null;
}
}
private Bitmap OverlayInkOnCapturedBitmap(Bitmap capturedBitmap, Rectangle captureArea, BitmapSource inkOverlayBitmapSource)
{
if (capturedBitmap == null || inkOverlayBitmapSource == null)
{
return capturedBitmap;
}
try
{
var virtualScreen = SystemInformation.VirtualScreen;
var sourceRect = new Rectangle(
captureArea.X - virtualScreen.X,
captureArea.Y - virtualScreen.Y,
captureArea.Width,
captureArea.Height);
sourceRect.Intersect(new Rectangle(0, 0, inkOverlayBitmapSource.PixelWidth, inkOverlayBitmapSource.PixelHeight));
if (sourceRect.Width <= 0 || sourceRect.Height <= 0)
{
return capturedBitmap;
}
using (var inkOverlayBitmap = ConvertBitmapSourceToBitmap(inkOverlayBitmapSource))
{
if (inkOverlayBitmap == null)
{
return capturedBitmap;
}
Bitmap resultBitmap = null;
try
{
resultBitmap = new Bitmap(capturedBitmap.Width, capturedBitmap.Height, PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(resultBitmap))
{
g.DrawImage(capturedBitmap, 0, 0, capturedBitmap.Width, capturedBitmap.Height);
var targetRect = new Rectangle(0, 0, Math.Min(sourceRect.Width, capturedBitmap.Width), Math.Min(sourceRect.Height, capturedBitmap.Height));
g.DrawImage(inkOverlayBitmap, targetRect, sourceRect, GraphicsUnit.Pixel);
}
return resultBitmap;
}
catch
{
resultBitmap?.Dispose();
throw;
}
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"叠加截图墨迹失败: {ex.Message}", LogHelper.LogType.Warning);
return capturedBitmap;
}
}
private Bitmap ConvertBitmapSourceToBitmap(BitmapSource bitmapSource)
{
if (bitmapSource == null)
{
return null;
}
using (var memoryStream = new MemoryStream())
{
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
encoder.Save(memoryStream);
memoryStream.Position = 0;
using (var tempBitmap = new Bitmap(memoryStream))
{
return new Bitmap(tempBitmap);
}
}
}
/// <summary>
/// 将截图插入到画布
/// </summary>
+50 -5
View File
@@ -162,10 +162,11 @@ namespace Ink_Canvas
var originalVisibility = Visibility;
try
{
var inkOverlayPreview = CreateInkOverlayPreviewBitmapSource();
Visibility = Visibility.Hidden;
await Task.Delay(200);
var screenshotResult = await ShowScreenshotSelector();
var screenshotResult = await ShowScreenshotSelector(inkOverlayPreview);
if (!screenshotResult.HasValue)
{
@@ -202,10 +203,32 @@ namespace Ink_Canvas
try
{
if (screenshotResult.Value.IncludeInk && screenshotResult.Value.InkOverlayBitmapSource != null)
{
var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Value.Area, screenshotResult.Value.InkOverlayBitmapSource);
if (withInkBitmap != null && withInkBitmap != finalBitmap)
{
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
{
finalBitmap.Dispose();
}
finalBitmap = withInkBitmap;
needDisposeFinalBitmap = true;
}
}
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
{
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
needDisposeFinalBitmap = true;
var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
if (maskedBitmap != null && maskedBitmap != finalBitmap)
{
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
{
finalBitmap.Dispose();
}
finalBitmap = maskedBitmap;
needDisposeFinalBitmap = true;
}
}
var directory = Path.GetDirectoryName(desktopPath);
@@ -275,10 +298,32 @@ namespace Ink_Canvas
try
{
if (screenshotResult.IncludeInk && screenshotResult.InkOverlayBitmapSource != null)
{
var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Area, screenshotResult.InkOverlayBitmapSource);
if (withInkBitmap != null && withInkBitmap != finalBitmap)
{
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
{
finalBitmap.Dispose();
}
finalBitmap = withInkBitmap;
needDisposeFinalBitmap = true;
}
}
if (screenshotResult.Path != null && screenshotResult.Path.Count > 0)
{
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Path, screenshotResult.Area);
needDisposeFinalBitmap = true;
var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Path, screenshotResult.Area);
if (maskedBitmap != null && maskedBitmap != finalBitmap)
{
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
{
finalBitmap.Dispose();
}
finalBitmap = maskedBitmap;
needDisposeFinalBitmap = true;
}
}
bitmapSourceForClipboard = ConvertBitmapToBitmapSource(finalBitmap);
@@ -43,6 +43,7 @@ namespace Ink_Canvas
else
{
HideSubPanels();
UpdateBorderDrawShapePosition();
AnimationsHelper.ShowWithSlideFromBottomAndFade(BorderDrawShape);
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderDrawShape);
}
@@ -36,6 +36,13 @@
</Rectangle.Clip>
</Rectangle>
<!-- 墨迹预览层(用于“包含墨迹”预览) -->
<Image Name="InkPreviewImage"
Stretch="Fill"
IsHitTestVisible="False"
Visibility="Collapsed"
Panel.ZIndex="950" />
<!-- 选择区域容器 -->
<Canvas Name="SelectionCanvas" Panel.ZIndex="1000">
<!-- 矩形选择模式 -->
@@ -141,6 +148,18 @@
Background="#404040" />
<!-- 操作按钮 -->
<CheckBox Name="IncludeInkCheckBox"
Content="包含墨迹"
Margin="4,0"
VerticalAlignment="Center"
Foreground="White"
Checked="IncludeInkCheckBox_Checked"
Unchecked="IncludeInkCheckBox_Unchecked" />
<Rectangle Name="ToolbarDividerRectangle"
Width="1"
Height="20"
Fill="#404040"
Margin="8,0" />
<Button Name="ConfirmButton"
Content="确认截图"
Margin="4,0"
@@ -7,6 +7,7 @@ using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using Brushes = System.Windows.Media.Brushes;
@@ -37,6 +38,7 @@ namespace Ink_Canvas
private Bitmap _capturedCameraImage = null;
private DateTime _lastBlankClickTime = DateTime.MinValue;
private WpfPoint _lastBlankClickPosition;
private readonly BitmapSource _inkOverlayPreview;
private const int DoubleClickTimeThresholdMs = 300; // 双击判定时间阈值(常见范围 200~500ms)
private const double DoubleClickDistanceThresholdPx = 12; // 双击判定位置阈值(像素)
@@ -55,9 +57,11 @@ namespace Ink_Canvas
public Bitmap CameraImage { get; private set; }
public System.Windows.Media.Imaging.BitmapSource CameraBitmapSource { get; private set; }
public bool ShouldAddToWhiteboard { get; private set; }
public bool IncludeInkInScreenshot { get; private set; }
public ScreenshotSelectorWindow()
public ScreenshotSelectorWindow(BitmapSource inkOverlayPreview = null)
{
_inkOverlayPreview = inkOverlayPreview;
InitializeComponent();
// 设置窗口覆盖所有屏幕
@@ -71,6 +75,7 @@ namespace Ink_Canvas
// 初始化按钮状态
InitializeButtonStates();
InitializeInkPreview();
// 初始化摄像头服务
InitializeCameraService();
@@ -106,6 +111,44 @@ namespace Ink_Canvas
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
}
private void InitializeInkPreview()
{
IncludeInkInScreenshot = false;
IncludeInkCheckBox.IsChecked = false;
if (_inkOverlayPreview != null)
{
InkPreviewImage.Source = _inkOverlayPreview;
}
else
{
IncludeInkCheckBox.IsEnabled = false;
IncludeInkCheckBox.Opacity = 0.6;
IncludeInkCheckBox.ToolTip = "当前无可预览墨迹";
}
UpdateInkPreviewVisibility();
}
private void IncludeInkCheckBox_Checked(object sender, RoutedEventArgs e)
{
IncludeInkInScreenshot = true;
UpdateInkPreviewVisibility();
}
private void IncludeInkCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
IncludeInkInScreenshot = false;
UpdateInkPreviewVisibility();
}
private void UpdateInkPreviewVisibility()
{
InkPreviewImage.Visibility = IncludeInkInScreenshot && _inkOverlayPreview != null
? Visibility.Visible
: Visibility.Collapsed;
}
private void InitializeCameraService()
{
try
@@ -370,6 +413,7 @@ namespace Ink_Canvas
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
IncludeInkCheckBox.IsEnabled = _inkOverlayPreview != null;
HintText.Text = "拖拽鼠标选择矩形区域";
HintTextBorder.Visibility = Visibility.Visible;
}
@@ -383,6 +427,7 @@ namespace Ink_Canvas
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
IncludeInkCheckBox.IsEnabled = _inkOverlayPreview != null;
HintText.Text = "按住鼠标左键自由绘制,松开后可继续调整或重新绘制,确认后再截图";
HintTextBorder.Visibility = Visibility.Visible;
}
@@ -402,6 +447,7 @@ namespace Ink_Canvas
// 隐藏摄像头预览
CameraPreviewBorder.Visibility = Visibility.Collapsed;
IncludeInkCheckBox.IsEnabled = _inkOverlayPreview != null;
// 直接执行全屏截图
PerformFullScreenCapture();
@@ -426,6 +472,8 @@ namespace Ink_Canvas
CameraPreviewBorder.Visibility = Visibility.Visible;
HintText.Text = "摄像头预览模式,点击确认截图按钮进行截图";
HintTextBorder.Visibility = Visibility.Visible;
IncludeInkCheckBox.IsEnabled = false;
IncludeInkCheckBox.IsChecked = false;
// 启动摄像头预览
if (_cameraService != null && _cameraService.HasAvailableCameras())
@@ -491,6 +539,7 @@ namespace Ink_Canvas
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
ShouldAddToWhiteboard = false;
IncludeInkInScreenshot = IncludeInkCheckBox.IsChecked == true;
// 在自由绘制模式下,按当前自由选区执行确认
if (_isFreehandMode)
@@ -512,6 +561,7 @@ namespace Ink_Canvas
private void AddToWhiteboardButton_Click(object sender, RoutedEventArgs e)
{
ShouldAddToWhiteboard = true;
IncludeInkInScreenshot = IncludeInkCheckBox.IsChecked == true;
// 在自由绘制模式下,按当前自由选区执行确认
if (_isFreehandMode)
@@ -578,10 +628,12 @@ namespace Ink_Canvas
if (hitElement != null && (
hitElement is Ellipse ||
hitElement is System.Windows.Controls.Button ||
hitElement is System.Windows.Controls.CheckBox ||
hitElement is Border ||
hitElement is TextBlock ||
hitElement is StackPanel ||
hitElement is Separator ||
hitElement.Name == "ToolbarDividerRectangle" ||
hitElement.Name == "SizeInfoBorder" ||
hitElement.Name == "HintText" ||
hitElement.Name == "AdjustModeHint" ||
@@ -669,10 +721,12 @@ namespace Ink_Canvas
if (hitElement != null && (
hitElement is Ellipse ||
hitElement is System.Windows.Controls.Button ||
hitElement is System.Windows.Controls.CheckBox ||
hitElement is Border ||
hitElement is TextBlock ||
hitElement is StackPanel ||
hitElement is Separator ||
hitElement.Name == "ToolbarDividerRectangle" ||
hitElement.Name == "SizeInfoBorder" ||
hitElement.Name == "HintText" ||
hitElement.Name == "AdjustModeHint"))
@@ -1178,6 +1232,7 @@ namespace Ink_Canvas
SelectedPath = null;
CameraImage = null;
ShouldAddToWhiteboard = false;
IncludeInkInScreenshot = false;
DialogResult = false;
Close();
}
+15 -13
View File
@@ -76,21 +76,23 @@
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CJKmkp"><img src="https://avatars.githubusercontent.com/u/113243675?v=4?s=100" width="100px;" alt="CJK_mkp"/><br /><sub><b>CJK_mkp</b></sub></a><br /><a href="#maintenance-CJKmkp" title="Maintenance">🚧</a> <a href="#doc-CJKmkp" title="Documentation">📖</a> <a href="#code-CJKmkp" title="Code">💻</a> <a href="#design-CJKmkp" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CreeperAWA"><img src="https://avatars.githubusercontent.com/u/134939494?v=4?s=100" width="100px;" alt="CreeperAWA"/><br /><sub><b>CreeperAWA</b></sub></a><br /><a href="#code-CreeperAWA" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/2-2-3-trimethylpentane"><img src="https://avatars.githubusercontent.com/u/141403762?v=4?s=100" width="100px;" alt="2,2,3-三甲基戊烷"/><br /><sub><b>2,2,3-三甲基戊烷</b></sub></a><br /><a href="#blog-2-2-3-trimethylpentane" title="Blogposts">📝</a> <a href="#doc-2-2-3-trimethylpentane" title="Documentation">📖</a> <a href="#design-2-2-3-trimethylpentane" title="Design">🎨</a> <a href="#test-2-2-3-trimethylpentane" title="Tests">⚠️</a> <a href="#tutorial-2-2-3-trimethylpentane" title="Tutorials">✅</a> <a href="#video-2-2-3-trimethylpentane" title="Videos">📹</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Alan-CRL"><img src="https://avatars.githubusercontent.com/u/92425617?v=4?s=100" width="100px;" alt="Alan-CRL"/><br /><sub><b>Alan-CRL</b></sub></a><br /><a href="#code-Alan-CRL" title="Code">💻</a> <a href="#infra-Alan-CRL" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#doc-Alan-CRL" title="Documentation">📖</a> <a href="#financial-Alan-CRL" title="Financial">💵</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MKStoler1024"><img src="https://avatars.githubusercontent.com/u/158786854?v=4?s=100" width="100px;" alt="MKStoler1024"/><br /><sub><b>MKStoler1024</b></sub></a><br /><a href="#doc-MKStoler1024" title="Documentation">📖</a> <a href="#code-MKStoler1024" title="Code">💻</a> <a href="#design-MKStoler1024" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/awesome-iwb"><img src="https://avatars.githubusercontent.com/u/184760810?v=4?s=100" width="100px;" alt="Awesome Iwb"/><br /><sub><b>Awesome Iwb</b></sub></a><br /><a href="#doc-awesome-iwb" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PrefacedCorg"><img src="https://avatars.githubusercontent.com/u/129855423?v=4?s=100" width="100px;" alt="PrefacedCorg"/><br /><sub><b>PrefacedCorg</b></sub></a><br /><a href="#code-PrefacedCorg" title="Code">💻</a> <a href="#design-PrefacedCorg" title="Design">🎨</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/CJKmkp"><img src="https://avatars.githubusercontent.com/u/113243675?v=4?s=100" width="100px;" alt="CJK_mkp"/><br /><sub><b>CJK_mkp</b></sub></a><br /><a href="#maintenance-CJKmkp" title="Maintenance">🚧</a> <a href="https://github.com/InkCanvasForClass/community/commits?author=CJKmkp" title="Documentation">📖</a> <a href="https://github.com/InkCanvasForClass/community/commits?author=CJKmkp" title="Code">💻</a> <a href="#design-CJKmkp" title="Design">🎨</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/CreeperAWA"><img src="https://avatars.githubusercontent.com/u/134939494?v=4?s=100" width="100px;" alt="CreeperAWA"/><br /><sub><b>CreeperAWA</b></sub></a><br /><a href="https://github.com/InkCanvasForClass/community/commits?author=CreeperAWA" title="Code">💻</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/2-2-3-trimethylpentane"><img src="https://avatars.githubusercontent.com/u/141403762?v=4?s=100" width="100px;" alt="2,2,3-三甲基戊烷"/><br /><sub><b>2,2,3-三甲基戊烷</b></sub></a><br /><a href="#blog-2-2-3-trimethylpentane" title="Blogposts">📝</a> <a href="https://github.com/InkCanvasForClass/community/commits?author=2-2-3-trimethylpentane" title="Documentation">📖</a> <a href="#design-2-2-3-trimethylpentane" title="Design">🎨</a> <a href="https://github.com/InkCanvasForClass/community/commits?author=2-2-3-trimethylpentane" title="Tests">⚠️</a> <a href="#tutorial-2-2-3-trimethylpentane" title="Tutorials">✅</a> <a href="#video-2-2-3-trimethylpentane" title="Videos">📹</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/Alan-CRL"><img src="https://avatars.githubusercontent.com/u/92425617?v=4?s=100" width="100px;" alt="Alan-CRL"/><br /><sub><b>Alan-CRL</b></sub></a><br /><a href="https://github.com/InkCanvasForClass/community/commits?author=Alan-CRL" title="Code">💻</a> <a href="#infra-Alan-CRL" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/InkCanvasForClass/community/commits?author=Alan-CRL" title="Documentation">📖</a> <a href="#financial-Alan-CRL" title="Financial">💵</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/MKStoler1024"><img src="https://avatars.githubusercontent.com/u/158786854?v=4?s=100" width="100px;" alt="MKStoler1024"/><br /><sub><b>MKStoler1024</b></sub></a><br /><a href="https://github.com/InkCanvasForClass/community/commits?author=MKStoler1024" title="Documentation">📖</a> <a href="https://github.com/InkCanvasForClass/community/commits?author=MKStoler1024" title="Code">💻</a> <a href="#design-MKStoler1024" title="Design">🎨</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://blog.jursin.top"><img src="https://avatars.githubusercontent.com/u/127487914?v=4?s=100" width="100px;" alt="Jursin"/><br /><sub><b>Jursin</b></sub></a><br /><a href="#design-Jursin" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Tayasui-rainnya"><img src="https://avatars.githubusercontent.com/u/156585442?v=4?s=100" width="100px;" alt="tayasui rainnya!"/><br /><sub><b>tayasui rainnya!</b></sub></a><br /><a href="#design-Tayasui-rainnya" title="Design">🎨</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/doudou0720"><img src="https://avatars.githubusercontent.com/u/98651603?v=4?s=100" width="100px;" alt="doudou0720"/><br /><sub><b>doudou0720</b></sub></a><br /><a href="#code-doudou0720" title="Code">💻</a> <a href="#doc-doudou0720" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PANDAJSR"><img src="https://avatars.githubusercontent.com/u/170189561?v=4?s=100" width="100px;" alt="PANDAJSR"/><br /><sub><b>PANDAJSR</b></sub></a><br /><a href="#code-PANDAJSR" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://lyxwx.top"><img src="https://avatars.githubusercontent.com/u/66517348?v=4?s=100" width="100px;" alt="流焰xwx"/><br /><sub><b>流焰xwx</b></sub></a><br /><a href="#code-LiuYan-xwx" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Super-Yyt"><img src="https://avatars.githubusercontent.com/u/206630707?v=4?s=100" width="100px;" alt="Super-Yyt"/><br /><sub><b>Super-Yyt</b></sub></a><br /><a href="#infra-Super-Yyt" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-Super-Yyt" title="Blogposts">📝</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/awesome-iwb"><img src="https://avatars.githubusercontent.com/u/184760810?v=4?s=100" width="100px;" alt="Awesome Iwb"/><br /><sub><b>Awesome Iwb</b></sub></a><br /><a href="https://github.com/InkCanvasForClass/community/commits?author=awesome-iwb" title="Documentation">📖</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/PrefacedCorg"><img src="https://avatars.githubusercontent.com/u/129855423?v=4?s=100" width="100px;" alt="PrefacedCorg"/><br /><sub><b>PrefacedCorg</b></sub></a><br /><a href="https://github.com/InkCanvasForClass/community/commits?author=PrefacedCorg" title="Code">💻</a> <a href="#design-PrefacedCorg" title="Design">🎨</a></td>
<td align="center" valign="top" width="20%"><a href="http://blog.jursin.top"><img src="https://avatars.githubusercontent.com/u/127487914?v=4?s=100" width="100px;" alt="Jursin"/><br /><sub><b>Jursin</b></sub></a><br /><a href="#design-Jursin" title="Design">🎨</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/Tayasui-rainnya"><img src="https://avatars.githubusercontent.com/u/156585442?v=4?s=100" width="100px;" alt="tayasui rainnya!"/><br /><sub><b>tayasui rainnya!</b></sub></a><br /><a href="#design-Tayasui-rainnya" title="Design">🎨</a> <a href="https://github.com/InkCanvasForClass/community/commits?author=Tayasui-rainnya" title="Code">💻</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/doudou0720"><img src="https://avatars.githubusercontent.com/u/98651603?v=4?s=100" width="100px;" alt="doudou0720"/><br /><sub><b>doudou0720</b></sub></a><br /><a href="https://github.com/InkCanvasForClass/community/commits?author=doudou0720" title="Code">💻</a> <a href="#blog-doudou0720" title="Blogposts">📝</a> <a href="#infra-doudou0720" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
</tr>
<tr>
<td align="center" valign="top" width="20%"><a href="https://github.com/PANDAJSR"><img src="https://avatars.githubusercontent.com/u/170189561?v=4?s=100" width="100px;" alt="PANDAJSR"/><br /><sub><b>PANDAJSR</b></sub></a><br /><a href="https://github.com/InkCanvasForClass/community/commits?author=PANDAJSR" title="Code">💻</a></td>
<td align="center" valign="top" width="20%"><a href="http://lyxwx.top"><img src="https://avatars.githubusercontent.com/u/66517348?v=4?s=100" width="100px;" alt="流焰xwx"/><br /><sub><b>流焰xwx</b></sub></a><br /><a href="https://github.com/InkCanvasForClass/community/commits?author=LiuYan-xwx" title="Code">💻</a></td>
<td align="center" valign="top" width="20%"><a href="https://github.com/Super-Yyt"><img src="https://avatars.githubusercontent.com/u/206630707?v=4?s=100" width="100px;" alt="Super-Yyt"/><br /><sub><b>Super-Yyt</b></sub></a><br /><a href="#infra-Super-Yyt" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-Super-Yyt" title="Blogposts">📝</a></td>
</tr>
</tbody>
</table>