improve:插入图片

This commit is contained in:
2025-07-21 12:44:05 +08:00
parent f641b282b6
commit 675959e615
8 changed files with 337 additions and 61 deletions
+24 -2
View File
@@ -3,10 +3,11 @@ using System.Collections.Generic;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows; // Added for UIElement
namespace Ink_Canvas.Helpers
{
public class TimeMachine
public partial class TimeMachine
{
private readonly List<TimeMachineHistory> _currentStrokeHistory = new List<TimeMachineHistory>();
@@ -139,6 +140,7 @@ namespace Ink_Canvas.Helpers
//这里说一下 Tuple的 Value1 是初始值 ; Value 2 是改变值
public Dictionary<Stroke, Tuple<StylusPointCollection, StylusPointCollection>> StylusPointDictionary;
public Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>> DrawingAttributes;
public UIElement InsertedElement; // 新增
public TimeMachineHistory(StrokeCollection currentStroke, TimeMachineHistoryType commitType, bool strokeHasBeenCleared)
{
CommitType = commitType;
@@ -163,6 +165,11 @@ namespace Ink_Canvas.Helpers
StrokeHasBeenCleared = strokeHasBeenCleared;
ReplacedStroke = replacedStroke;
}
public TimeMachineHistory(UIElement element, TimeMachineHistoryType commitType) // 新增
{
CommitType = commitType;
InsertedElement = element;
}
}
public enum TimeMachineHistoryType
@@ -171,6 +178,21 @@ namespace Ink_Canvas.Helpers
ShapeRecognition,
Clear,
Manipulation,
DrawingAttributes
DrawingAttributes,
ElementInsert // 新增
}
public partial class TimeMachine // 新增partial,便于扩展
{
public void CommitElementInsertHistory(UIElement element)
{
if (_currentIndex + 1 < _currentStrokeHistory.Count)
{
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
}
_currentStrokeHistory.Add(new TimeMachineHistory(element, TimeMachineHistoryType.ElementInsert));
_currentIndex = _currentStrokeHistory.Count - 1;
NotifyUndoRedoState();
}
}
}
@@ -12,6 +12,9 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml.Linq;
using System.Windows.Controls;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
namespace Ink_Canvas {
public partial class MainWindow : Window {
@@ -23,6 +26,7 @@ namespace Ink_Canvas {
private int WhiteboardTotalCount = 1;
private TimeMachineHistory[][] TimeMachineHistories = new TimeMachineHistory[101][]; //最多99页,0用来存储非白板时的墨迹以便还原
// 保存每页白板图片信息
private void SaveStrokes(bool isBackupMain = false) {
if (isBackupMain) {
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
@@ -32,6 +36,26 @@ namespace Ink_Canvas {
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory;
timeMachine.ClearStrokeHistory();
// 保存当前页图片信息
var elementInfos = new List<CanvasElementInfo>();
foreach (var child in inkCanvas.Children)
{
if (child is Image img && img.Source is BitmapImage bmp)
{
elementInfos.Add(new CanvasElementInfo
{
Type = "Image",
SourcePath = bmp.UriSource?.LocalPath ?? "",
Left = InkCanvas.GetLeft(img),
Top = InkCanvas.GetTop(img),
Width = img.Width,
Height = img.Height
});
}
}
var savePath = Settings.Automation.AutoSavedStrokesLocation;
if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
File.WriteAllText(System.IO.Path.Combine(savePath, $"elements_page{CurrentWhiteboardIndex}.json"), JsonConvert.SerializeObject(elementInfos, Formatting.Indented));
}
}
@@ -42,6 +66,7 @@ namespace Ink_Canvas {
_currentCommitType = CommitReason.UserInput;
}
// 恢复每页白板图片信息
private void RestoreStrokes(bool isBackupMain = false) {
try {
if (TimeMachineHistories[CurrentWhiteboardIndex] == null) return; //防止白板打开后不居中
@@ -51,6 +76,29 @@ namespace Ink_Canvas {
} else {
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[CurrentWhiteboardIndex]);
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item);
// 恢复当前页图片信息
inkCanvas.Children.Clear();
var savePath = Settings.Automation.AutoSavedStrokesLocation;
var elementsFile = System.IO.Path.Combine(savePath, $"elements_page{CurrentWhiteboardIndex}.json");
if (File.Exists(elementsFile))
{
var elementInfos = JsonConvert.DeserializeObject<List<CanvasElementInfo>>(File.ReadAllText(elementsFile));
foreach (var info in elementInfos)
{
if (info.Type == "Image" && File.Exists(info.SourcePath))
{
var img = new Image
{
Source = new BitmapImage(new Uri(info.SourcePath)),
Width = info.Width,
Height = info.Height
};
InkCanvas.SetLeft(img, info.Left);
InkCanvas.SetTop(img, info.Top);
inkCanvas.Children.Add(img);
}
}
}
}
}
catch {
@@ -0,0 +1,181 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
namespace Ink_Canvas
{
public partial class MainWindow : Window
{
#region Image
private async void BtnImageInsert_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Image files (*.jpg; *.jpeg; *.png; *.bmp)|*.jpg;*.jpeg;*.png;*.bmp";
if (openFileDialog.ShowDialog() == true)
{
string filePath = openFileDialog.FileName;
Image image = await CreateAndCompressImageAsync(filePath);
if (image != null)
{
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
image.Name = timestamp;
CenterAndScaleElement(image);
InkCanvas.SetLeft(image, 0);
InkCanvas.SetTop(image, 0);
inkCanvas.Children.Add(image);
timeMachine.CommitElementInsertHistory(image);
}
}
}
private async Task<Image> CreateAndCompressImageAsync(string filePath)
{
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
if (!Directory.Exists(savePath))
{
Directory.CreateDirectory(savePath);
}
string fileExtension = Path.GetExtension(filePath);
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
string newFilePath = Path.Combine(savePath, timestamp + fileExtension);
await Task.Run(() => File.Copy(filePath, newFilePath, true));
return await Dispatcher.InvokeAsync(() =>
{
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(newFilePath);
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
int width = bitmapImage.PixelWidth;
int height = bitmapImage.PixelHeight;
Image image = new Image();
if (isLoaded && Settings.Canvas.IsCompressPicturesUploaded && (width > 1920 || height > 1080))
{
double scaleX = 1920.0 / width;
double scaleY = 1080.0 / height;
double scale = Math.Min(scaleX, scaleY);
TransformedBitmap transformedBitmap = new TransformedBitmap(bitmapImage, new ScaleTransform(scale, scale));
image.Source = transformedBitmap;
image.Width = transformedBitmap.PixelWidth;
image.Height = transformedBitmap.PixelHeight;
}
else
{
image.Source = bitmapImage;
image.Width = width;
image.Height = height;
}
return image;
});
}
#endregion
#region Media
private async void BtnMediaInsert_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Media files (*.mp4; *.avi; *.wmv)|*.mp4;*.avi;*.wmv";
if (openFileDialog.ShowDialog() == true)
{
string filePath = openFileDialog.FileName;
byte[] mediaBytes = await Task.Run(() => File.ReadAllBytes(filePath));
MediaElement mediaElement = await CreateMediaElementAsync(filePath);
if (mediaElement != null)
{
CenterAndScaleElement(mediaElement);
InkCanvas.SetLeft(mediaElement, 0);
InkCanvas.SetTop(mediaElement, 0);
inkCanvas.Children.Add(mediaElement);
mediaElement.LoadedBehavior = MediaState.Manual;
mediaElement.UnloadedBehavior = MediaState.Manual;
mediaElement.Loaded += async (_, args) =>
{
mediaElement.Play();
await Task.Delay(100);
mediaElement.Pause();
};
timeMachine.CommitElementInsertHistory(mediaElement);
}
}
}
private async Task<MediaElement> CreateMediaElementAsync(string filePath)
{
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
if (!Directory.Exists(savePath))
{
Directory.CreateDirectory(savePath);
}
return await Dispatcher.InvokeAsync(() =>
{
MediaElement mediaElement = new MediaElement();
mediaElement.Source = new Uri(filePath);
string timestamp = "media_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
mediaElement.Name = timestamp;
mediaElement.LoadedBehavior = MediaState.Manual;
mediaElement.UnloadedBehavior = MediaState.Manual;
mediaElement.Width = 256;
mediaElement.Height = 256;
string fileExtension = Path.GetExtension(filePath);
string newFilePath = Path.Combine(savePath, mediaElement.Name + fileExtension);
File.Copy(filePath, newFilePath, true);
mediaElement.Source = new Uri(newFilePath);
return mediaElement;
});
}
#endregion
private void CenterAndScaleElement(FrameworkElement element)
{
double maxWidth = SystemParameters.PrimaryScreenWidth / 2;
double maxHeight = SystemParameters.PrimaryScreenHeight / 2;
double scaleX = maxWidth / element.Width;
double scaleY = maxHeight / element.Height;
double scale = Math.Min(scaleX, scaleY);
TransformGroup transformGroup = new TransformGroup();
transformGroup.Children.Add(new ScaleTransform(scale, scale));
double canvasWidth = inkCanvas.ActualWidth;
double canvasHeight = inkCanvas.ActualHeight;
double centerX = (canvasWidth - element.Width * scale) / 2;
double centerY = (canvasHeight - element.Height * scale) / 2;
transformGroup.Children.Add(new TranslateTransform(centerX, centerY));
element.RenderTransform = transformGroup;
}
}
}
+21 -57
View File
@@ -1766,6 +1766,9 @@ namespace Ink_Canvas {
ClearStrokes(true);
RestoreStrokes();
// 退出白板时清空图片
inkCanvas.Children.Clear();
if (BtnSwitchTheme.Content.ToString() == "浅色") {
BtnSwitch.Content = "黑板";
BtnExit.Foreground = Brushes.White;
@@ -1774,12 +1777,10 @@ namespace Ink_Canvas {
BtnSwitch.Content = "白板";
if (isPresentationHaveBlackSpace) {
BtnExit.Foreground = Brushes.White;
//SymbolIconBtnColorBlackContent.Foreground = Brushes.White;
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
}
else {
BtnExit.Foreground = Brushes.Black;
//SymbolIconBtnColorBlackContent.Foreground = Brushes.White;
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
}
}
@@ -1803,22 +1804,22 @@ namespace Ink_Canvas {
ClearStrokes(true);
RestoreStrokes(true);
// 退出白板时清空图片
inkCanvas.Children.Clear();
if (BtnSwitchTheme.Content.ToString() == "浅色") {
BtnSwitch.Content = "黑板";
BtnExit.Foreground = Brushes.White;
//SymbolIconBtnColorBlackContent.Foreground = Brushes.Black;
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
}
else {
BtnSwitch.Content = "白板";
if (isPresentationHaveBlackSpace) {
BtnExit.Foreground = Brushes.White;
//SymbolIconBtnColorBlackContent.Foreground = Brushes.White;
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
}
else {
BtnExit.Foreground = Brushes.Black;
//SymbolIconBtnColorBlackContent.Foreground = Brushes.White;
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
}
}
@@ -1840,12 +1841,10 @@ namespace Ink_Canvas {
BtnSwitch.Content = "屏幕";
if (BtnSwitchTheme.Content.ToString() == "浅色") {
BtnExit.Foreground = Brushes.White;
//SymbolIconBtnColorBlackContent.Foreground = Brushes.Black;
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
}
else {
BtnExit.Foreground = Brushes.Black;
//SymbolIconBtnColorBlackContent.Foreground = Brushes.White;
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
}
@@ -1990,53 +1989,6 @@ namespace Ink_Canvas {
#endregion
/// <summary>
/// 创建并压缩图片的异步方法
/// </summary>
private async Task<Image> CreateAndCompressImageAsync(string filePath)
{
string savePath = System.IO.Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
if (!System.IO.Directory.Exists(savePath))
System.IO.Directory.CreateDirectory(savePath);
string fileExtension = System.IO.Path.GetExtension(filePath);
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
string newFilePath = System.IO.Path.Combine(savePath, timestamp + fileExtension);
await Task.Run(() => System.IO.File.Copy(filePath, newFilePath, true));
return await Dispatcher.InvokeAsync(() =>
{
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(newFilePath);
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
int width = bitmapImage.PixelWidth;
int height = bitmapImage.PixelHeight;
Image image = new Image();
if (Settings.Canvas.IsCompressPicturesUploaded && (width > 1920 || height > 1080))
{
double scaleX = 1920.0 / width;
double scaleY = 1080.0 / height;
double scale = Math.Min(scaleX, scaleY);
var transformedBitmap = new TransformedBitmap(bitmapImage, new ScaleTransform(scale, scale));
image.Source = transformedBitmap;
image.Width = transformedBitmap.PixelWidth;
image.Height = transformedBitmap.PixelHeight;
}
else
{
image.Source = bitmapImage;
image.Width = width;
image.Height = height;
}
return image;
});
}
private async void InsertImage_MouseUp(object sender, MouseButtonEventArgs e)
{
var dialog = new Microsoft.Win32.OpenFileDialog
@@ -2045,9 +1997,21 @@ namespace Ink_Canvas {
};
if (dialog.ShowDialog() == true)
{
var image = await CreateAndCompressImageAsync(dialog.FileName);
// TODO: 这里可以将image添加到画布或其他控件
MessageBox.Show("图片已处理完成,可在此处插入到画布。");
string filePath = dialog.FileName;
Image image = await CreateAndCompressImageAsync(filePath); // 补充image定义
if (image != null)
{
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
image.Name = timestamp;
CenterAndScaleElement(image);
InkCanvas.SetLeft(image, 0);
InkCanvas.SetTop(image, 0);
inkCanvas.Children.Add(image);
timeMachine.CommitElementInsertHistory(image);
}
}
}
}
@@ -13,8 +13,19 @@ using System.Windows.Forms;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
using System.Collections.Generic;
using System.Windows.Controls;
using Newtonsoft.Json;
namespace Ink_Canvas {
// 1. 定义元素信息结构
public class CanvasElementInfo
{
public string Type { get; set; } // "Image"
public string SourcePath { get; set; }
public double Left { get; set; }
public double Top { get; set; }
public double Width { get; set; }
public double Height { get; set; }
}
public partial class MainWindow : Window {
private void SymbolIconSaveStrokes_MouseUp(object sender, MouseButtonEventArgs e) {
if (lastBorderMouseDownObject != sender || inkCanvas.Visibility != Visibility.Visible) return;
@@ -108,6 +119,24 @@ namespace Ink_Canvas {
var fs = new FileStream(savePathWithName, FileMode.Create);
inkCanvas.Strokes.Save(fs);
fs.Close();
// 保存元素信息
var elementInfos = new List<CanvasElementInfo>();
foreach (var child in inkCanvas.Children)
{
if (child is Image img && img.Source is BitmapImage bmp)
{
elementInfos.Add(new CanvasElementInfo
{
Type = "Image",
SourcePath = bmp.UriSource?.LocalPath ?? "",
Left = InkCanvas.GetLeft(img),
Top = InkCanvas.GetTop(img),
Width = img.Width,
Height = img.Height
});
}
}
File.WriteAllText(Path.ChangeExtension(savePathWithName, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Formatting.Indented));
if (newNotice) ShowNotification("墨迹成功保存至 " + savePathWithName);
}
}
@@ -573,6 +602,28 @@ namespace Ink_Canvas {
}
}
// 恢复元素信息
var elementsFile = Path.ChangeExtension(filePath, ".elements.json");
if (File.Exists(elementsFile))
{
var elementInfos = JsonConvert.DeserializeObject<List<CanvasElementInfo>>(File.ReadAllText(elementsFile));
foreach (var info in elementInfos)
{
if (info.Type == "Image" && File.Exists(info.SourcePath))
{
var img = new Image
{
Source = new BitmapImage(new Uri(info.SourcePath)),
Width = info.Width,
Height = info.Height
};
InkCanvas.SetLeft(img, info.Left);
InkCanvas.SetTop(img, info.Top);
inkCanvas.Children.Add(img);
}
}
}
if (fileStreamHasNoStroke)
using (var ms = new MemoryStream(File.ReadAllBytes(filePath))) {
ms.Seek(0, SeekOrigin.Begin);
@@ -130,6 +130,16 @@ namespace Ink_Canvas {
if (canvas.Strokes.Contains(currentStroke))
canvas.Strokes.Remove(currentStroke);
}
} else if (item.CommitType == TimeMachineHistoryType.ElementInsert) {
if (!item.StrokeHasBeenCleared) {
// Undo: 移除元素
if (item.InsertedElement != null && inkCanvas.Children.Contains(item.InsertedElement))
inkCanvas.Children.Remove(item.InsertedElement);
} else {
// Redo: 添加元素
if (item.InsertedElement != null && !inkCanvas.Children.Contains(item.InsertedElement))
inkCanvas.Children.Add(item.InsertedElement);
}
}
_currentCommitType = CommitReason.UserInput;
+1 -1
View File
@@ -76,7 +76,7 @@ namespace Ink_Canvas
[JsonProperty("hyperbolaAsymptoteOption")]
public OptionalOperation HyperbolaAsymptoteOption { get; set; } = OptionalOperation.Ask;
[JsonProperty("isCompressPicturesUploaded")]
public bool IsCompressPicturesUploaded { get; set; } = true;
public bool IsCompressPicturesUploaded { get; set; } = false;
}
public enum OptionalOperation
@@ -12,7 +12,7 @@ TRACE;DEBUG;NETFRAMEWORK;NET472;;NET30_OR_GREATER;NET35_OR_GREATER;NET40_OR_GREA
E:\ICC CE\ICC CE main\community\Ink Canvas\App.xaml
21348134359
74373288771
752071346691
471037513499
Helpers\Plugins\BuiltIn\SuperLauncher\LauncherSettingsControl.xaml;Helpers\Plugins\BuiltIn\SuperLauncher\LauncherWindow.xaml;MainWindow.xaml;MainWindow_cs\MW_Eraser.xaml;Resources\DrawShapeImageDictionary.xaml;Resources\IconImageDictionary.xaml;Resources\SeewoImageDictionary.xaml;Resources\Styles\Dark.xaml;Resources\Styles\Light.xaml;Windows\AddCustomIconWindow.xaml;Windows\AddPickNameBackgroundWindow.xaml;Windows\CountdownTimerWindow.xaml;Windows\CustomIconWindow.xaml;Windows\CycleProcessBar.xaml;Windows\HasNewUpdateWindow.xaml;Windows\ManagePickNameBackgroundsWindow.xaml;Windows\NamesInputWindow.xaml;Windows\OperatingGuideWindow.xaml;Windows\PluginSettingsWindow.xaml;Windows\RandWindow.xaml;Windows\YesOrNoNotificationWindow.xaml;