Skip to content

Commit 5ad9ced

Browse files
committed
Added the Mirroring Feature.
1 parent 99473e4 commit 5ad9ced

File tree

2 files changed

+106
-3
lines changed

2 files changed

+106
-3
lines changed

ScanStack/Services/ImageEditor.cs

+87-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
using System.Numerics;
2-
using System.Runtime.InteropServices.WindowsRuntime;
3-
using CommunityToolkit.Mvvm.ComponentModel;
1+
using CommunityToolkit.Mvvm.ComponentModel;
42
using CommunityToolkit.Mvvm.Input;
53
using Microsoft.Graphics.Canvas;
64
using Microsoft.UI;
75
using Microsoft.UI.Xaml.Media.Imaging;
86
using ScanStack.Core.Models;
97
using ScanStack.Helpers;
8+
using System.Numerics;
9+
using System.Runtime.InteropServices.WindowsRuntime;
1010
using Windows.Foundation;
1111
using Windows.Graphics.Imaging;
1212
using Windows.Storage;
@@ -20,6 +20,7 @@ public partial class ImageEditor : ObservableObject
2020

2121
[ObservableProperty]
2222
[NotifyCanExecuteChangedFor(nameof(RotateImageCommand))]
23+
[NotifyCanExecuteChangedFor(nameof(MirrorImageCommand))]
2324
private FileModel? _selectedFile;
2425

2526
private bool CanExecute => SelectedFile != null;
@@ -62,6 +63,89 @@ private async Task RotateImage(object angleParam)
6263
FileChanged?.Invoke(selectedFile, outputFile.Path);
6364
}
6465

66+
[RelayCommand(CanExecute = nameof(CanExecute))]
67+
private async Task MirrorImage(object mirrorHorizontally)
68+
{
69+
bool MirrorHorizontally = bool.Parse(mirrorHorizontally.ToString()!);
70+
var selectedFile = SelectedFile!;
71+
var pictures = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
72+
var savePath = Path.Combine(pictures.SaveFolder.Path, "ScanStack");
73+
if (!Path.Exists(savePath))
74+
{
75+
Directory.CreateDirectory(savePath);
76+
}
77+
78+
var saveFolder = await StorageFolder.GetFolderFromPathAsync(savePath);
79+
var inputFile = await StorageFile.GetFileFromPathAsync(selectedFile.Path);
80+
var fileFormat = BitmapFileFormat.Png;
81+
switch (Path.GetExtension(selectedFile.Path))
82+
{
83+
case ".jpg" or ".jpeg":
84+
fileFormat = BitmapFileFormat.Jpeg;
85+
break;
86+
case ".png":
87+
fileFormat = BitmapFileFormat.Png;
88+
break;
89+
case ".tiff":
90+
fileFormat = BitmapFileFormat.Tiff;
91+
break;
92+
case ".bmp":
93+
fileFormat = BitmapFileFormat.Bmp;
94+
break;
95+
default:
96+
break;
97+
}
98+
var outputFile = await saveFolder.CreateFileAsync($"CopyOf_{Path.GetFileName(selectedFile.Path)}", CreationCollisionOption.ReplaceExisting);
99+
using var inputStream = await inputFile.OpenAsync(FileAccessMode.Read);
100+
using var outputStream = await outputFile.OpenAsync(FileAccessMode.ReadWrite);
101+
await MirrorImageAsync(inputStream, outputStream, fileFormat, MirrorHorizontally);
102+
FileChanged?.Invoke(selectedFile, outputFile.Path);
103+
}
104+
public static async Task MirrorImageAsync(IRandomAccessStream inputStream, IRandomAccessStream outputStream, BitmapFileFormat bitmapFileFormat, bool mirrorHorizontally = true)
105+
{
106+
var device = CanvasDevice.GetSharedDevice();
107+
108+
using var sourceBitmap = await CanvasBitmap.LoadAsync(device, inputStream);
109+
var sourceWidth = (float)sourceBitmap.Size.Width;
110+
var sourceHeight = (float)sourceBitmap.Size.Height;
111+
112+
var center = new Vector2(sourceWidth / 2, sourceHeight / 2);
113+
114+
using var renderTarget = new CanvasRenderTarget(device, sourceWidth, sourceHeight, sourceBitmap.Dpi);
115+
using (var session = renderTarget.CreateDrawingSession())
116+
{
117+
session.Clear(Colors.Transparent);
118+
119+
// Apply mirroring transformations
120+
var transform = Matrix3x2.Identity;
121+
122+
if (mirrorHorizontally)
123+
{
124+
transform *= Matrix3x2.CreateScale(-1, 1, center);
125+
}
126+
else
127+
{
128+
transform *= Matrix3x2.CreateScale(1, -1, center);
129+
}
130+
131+
session.Transform = transform;
132+
session.DrawImage(sourceBitmap, new Rect(0, 0, sourceWidth, sourceHeight));
133+
}
134+
135+
var pixelBytes = renderTarget.GetPixelBytes();
136+
var bitmapEncoder = await BitmapEncoder.CreateAsync(GetEncoderId(bitmapFileFormat), outputStream);
137+
bitmapEncoder.SetPixelData(
138+
BitmapPixelFormat.Bgra8,
139+
BitmapAlphaMode.Premultiplied,
140+
(uint)sourceWidth,
141+
(uint)sourceHeight,
142+
sourceBitmap.Dpi,
143+
sourceBitmap.Dpi,
144+
pixelBytes
145+
);
146+
await bitmapEncoder.FlushAsync();
147+
}
148+
65149
public static async Task RotateImageAsync(IRandomAccessStream inputStream, IRandomAccessStream outputStream, float rotationAngle, BitmapFileFormat bitmapFileFormat)
66150
{
67151
var device = CanvasDevice.GetSharedDevice();

ScanStack/Views/ScanPage.xaml

+19
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,25 @@
305305
</Button.Content>
306306
</Button>
307307
<AppBarSeparator />
308+
<Button
309+
x:Name="MirrorHButton"
310+
Command="{x:Bind ViewModel.ScanFileManager.ImageEditor.MirrorImageCommand}"
311+
CommandParameter="True"
312+
ToolTipService.ToolTip="Mirror Horizontally">
313+
<Button.Content>
314+
<FontIcon Glyph="&#xF587;" />
315+
</Button.Content>
316+
</Button>
317+
<Button
318+
x:Name="MirrorVButton"
319+
Command="{x:Bind ViewModel.ScanFileManager.ImageEditor.MirrorImageCommand}"
320+
CommandParameter="False"
321+
ToolTipService.ToolTip="Mirror Vertically">
322+
<Button.Content>
323+
<FontIcon Glyph="&#xF589;" />
324+
</Button.Content>
325+
</Button>
326+
<AppBarSeparator />
308327
<Button
309328
x:Name="RemoveFileButton"
310329
Command="{x:Bind ViewModel.ScanFileManager.RemoveSelectedFilesCommand}"

0 commit comments

Comments
 (0)