From d8b58b1538f8ab2b375c0557d2f3a7030f74686b Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 21 Aug 2018 17:23:26 +0200 Subject: [PATCH] Small changes to make the code more generic, so it's better possible to migrate. --- src/Greenshot.Core/Capture.cs | 13 +++++-- src/Greenshot.Core/CaptureElement.cs | 34 ++++++++++++++++--- src/Greenshot.Core/CaptureFlow.cs | 18 +++++----- .../Extensions/BitmapSourceExtensions.cs | 11 ++++++ .../InteropWindowCaptureExtensions.cs | 26 +++++++------- src/Greenshot.Core/Interfaces/ICapture.cs | 4 +-- .../Interfaces/ICaptureElement.cs | 6 ++-- src/Greenshot.Core/Interfaces/IDestination.cs | 4 +-- src/Greenshot.Core/Interfaces/IProcessor.cs | 4 +-- src/Greenshot.Core/Interfaces/ISource.cs | 4 +-- src/Greenshot.Core/Interfaces/ITemplate.cs | 4 +-- src/Greenshot.Core/Sources/DwmWindowSource.cs | 5 +-- src/Greenshot.Core/Sources/MouseSource.cs | 11 +++--- src/Greenshot.Core/Sources/ScreenSource.cs | 14 ++++---- .../Templates/CroppedTemplate.cs | 5 +-- .../Templates/SimpleTemplate.cs | 6 ++-- src/Greenshot.Tests/CaptureTests.cs | 16 ++++++--- 17 files changed, 119 insertions(+), 66 deletions(-) diff --git a/src/Greenshot.Core/Capture.cs b/src/Greenshot.Core/Capture.cs index b77fe6f6e..2043fdffd 100644 --- a/src/Greenshot.Core/Capture.cs +++ b/src/Greenshot.Core/Capture.cs @@ -31,13 +31,22 @@ using Greenshot.Core.Interfaces; namespace Greenshot.Core { /// - public class Capture : ICapture + public class Capture : ICapture { + public void Dispose() + { + foreach (var captureElement in CaptureElements) + { + captureElement?.Dispose(); + } + CaptureElements.Clear(); + } + /// public DateTimeOffset Taken { get; } = DateTimeOffset.Now; /// - public IList CaptureElements { get; } = new List(); + public IList> CaptureElements { get; } = new List>(); /// public NativeRect Bounds => CaptureElements.Select(element => element.Bounds).Aggregate((b1, b2) => b1.Union(b2)); diff --git a/src/Greenshot.Core/CaptureElement.cs b/src/Greenshot.Core/CaptureElement.cs index 789a55937..fda4b0cb1 100644 --- a/src/Greenshot.Core/CaptureElement.cs +++ b/src/Greenshot.Core/CaptureElement.cs @@ -21,8 +21,10 @@ #endregion +using System; using System.Collections.Generic; -using System.Windows.Media; +using System.Drawing; +using System.Windows.Media.Imaging; using Dapplo.Windows.Common.Structs; using Greenshot.Core.Enums; using Greenshot.Core.Interfaces; @@ -32,11 +34,25 @@ namespace Greenshot.Core /// /// The CaptureElement contains the information of an element in a capture, e.g the window and mouse /// - public class CaptureElement : ICaptureElement + public class CaptureElement : ICaptureElement { - public CaptureElement(NativePoint location, ImageSource content) + public CaptureElement(NativePoint location, TContent content) { - Bounds = new NativeRect(location, new NativeSize((int)content.Width, (int)content.Height)); + NativeSize size; + if (content is BitmapSource bitmapSource) + { + size = new NativeSize((int)bitmapSource.Width, (int)bitmapSource.Height); + } + else if (content is Bitmap bitmap) + { + size = new NativeSize(bitmap.Width, bitmap.Height); + } + else + { + throw new NotSupportedException(typeof(TContent).ToString()); + } + + Bounds = new NativeRect(location, size); Content = content; } @@ -44,12 +60,20 @@ namespace Greenshot.Core public NativeRect Bounds { get; set; } /// - public ImageSource Content { get; set; } + public TContent Content { get; set; } /// public CaptureElementType ElementType { get; set; } = CaptureElementType.Unknown; /// public IDictionary MetaData { get; } = new Dictionary(); + + public void Dispose() + { + if (Content is IDisposable disposable) + { + disposable.Dispose(); + } + } } } diff --git a/src/Greenshot.Core/CaptureFlow.cs b/src/Greenshot.Core/CaptureFlow.cs index cfaa84bb8..42d3df2be 100644 --- a/src/Greenshot.Core/CaptureFlow.cs +++ b/src/Greenshot.Core/CaptureFlow.cs @@ -31,40 +31,40 @@ namespace Greenshot.Core /// /// This describes a capture flow, from source via processor to destination /// - public class CaptureFlow + public class CaptureFlow { /// /// The ISource this capture flow contains /// - public IList Sources + public IList> Sources { get; - } = new List(); + } = new List>(); /// /// The IProcessor this capture flow contains /// - public IList Processors + public IList> Processors { get; - } = new List(); + } = new List>(); /// /// The IDestination this capture flow contains /// - public IList Destinations + public IList> Destinations { get; - } = new List(); + } = new List>(); /// /// Execute this capture flow, to create a capture /// /// CancellationToken /// ICapture - public async Task Execute(CancellationToken cancellationToken = default) + public async Task> Execute(CancellationToken cancellationToken = default) { - var capture = new Capture(); + var capture = new Capture(); // Import the capture from the sources foreach (var source in Sources) diff --git a/src/Greenshot.Core/Extensions/BitmapSourceExtensions.cs b/src/Greenshot.Core/Extensions/BitmapSourceExtensions.cs index ec6aabd85..7a4af3688 100644 --- a/src/Greenshot.Core/Extensions/BitmapSourceExtensions.cs +++ b/src/Greenshot.Core/Extensions/BitmapSourceExtensions.cs @@ -24,6 +24,7 @@ using System; using System.IO; using System.Windows.Media.Imaging; +using Dapplo.Windows.Common.Structs; using Greenshot.Core.Enums; namespace Greenshot.Core.Extensions @@ -66,5 +67,15 @@ namespace Greenshot.Core.Extensions return returnStream; } + + /// + /// Create a NativeSizeFloat object from a BitmapSource + /// + /// + /// + public static NativeSizeFloat Size(this BitmapSource bitmapSource) + { + return new NativeSizeFloat(bitmapSource.Width, bitmapSource.Height); + } } } diff --git a/src/Greenshot.Core/Extensions/InteropWindowCaptureExtensions.cs b/src/Greenshot.Core/Extensions/InteropWindowCaptureExtensions.cs index b3daf1738..bace08038 100644 --- a/src/Greenshot.Core/Extensions/InteropWindowCaptureExtensions.cs +++ b/src/Greenshot.Core/Extensions/InteropWindowCaptureExtensions.cs @@ -71,10 +71,10 @@ namespace Greenshot.Core.Extensions /// InteropWindow /// true to use the client bounds /// ICaptureElement - public static ICaptureElement CaptureFromScreen(this IInteropWindow interopWindow, bool clientBounds = false) + public static ICaptureElement CaptureFromScreen(this IInteropWindow interopWindow, bool clientBounds = false) { var bounds = clientBounds ? interopWindow.GetInfo().ClientBounds: interopWindow.GetInfo().Bounds; - ICaptureElement result = ScreenSource.CaptureRectangle(bounds); + ICaptureElement result = ScreenSource.CaptureRectangle(bounds); return result; } @@ -84,18 +84,18 @@ namespace Greenshot.Core.Extensions /// TODO: If there is a parent, this could be removed with SetParent, and set back afterwards. /// /// ICaptureElement - public static ICaptureElement PrintWindow(this IInteropWindow interopWindow) + public static ICaptureElement PrintWindow(this IInteropWindow interopWindow) { var returnBitmap = interopWindow.PrintWindow(); if (interopWindow.HasParent || !interopWindow.IsMaximized()) { - return new CaptureElement(interopWindow.GetInfo().Bounds.Location, returnBitmap); + return new CaptureElement(interopWindow.GetInfo().Bounds.Location, returnBitmap); } Log.Debug().WriteLine("Correcting for maximalization"); var borderSize = interopWindow.GetInfo().BorderSize; var bounds = interopWindow.GetInfo().Bounds; var borderRectangle = new NativeRect(borderSize.Width, borderSize.Height, bounds.Width - 2 * borderSize.Width, bounds.Height - 2 * borderSize.Height); - return new CaptureElement(interopWindow.GetInfo().Bounds.Location, new CroppedBitmap(returnBitmap, borderRectangle)); + return new CaptureElement(interopWindow.GetInfo().Bounds.Location, new CroppedBitmap(returnBitmap, borderRectangle)); } @@ -103,6 +103,7 @@ namespace Greenshot.Core.Extensions /// Helper method to check if it is allowed to capture the process using GDI /// /// Process owning the window + /// ICaptureConfiguration /// true if it's allowed public static bool IsGdiAllowed(Process process, ICaptureConfiguration captureConfiguration) { @@ -137,10 +138,10 @@ namespace Greenshot.Core.Extensions /// IInteropWindow /// ICaptureConfiguration configuration for the settings /// ICaptureElement with the capture - public static async ValueTask CaptureDwmWindow(this IInteropWindow interopWindow, ICaptureConfiguration captureConfiguration) + public static async ValueTask> CaptureDwmWindow(this IInteropWindow interopWindow, ICaptureConfiguration captureConfiguration) { // The capture - ICaptureElement capturedBitmap = null; + ICaptureElement capturedBitmap = null; var thumbnailHandle = IntPtr.Zero; Form tempForm = null; var tempFormShown = false; @@ -387,12 +388,11 @@ namespace Greenshot.Core.Extensions /// ICaptureElement with the black image /// ICaptureElement with the white image /// ICaptureElement with transparency - private static ICaptureElement ApplyTransparency(ICaptureElement blackBitmap, ICaptureElement whiteBitmap) + private static ICaptureElement ApplyTransparency(ICaptureElement blackBitmap, ICaptureElement whiteBitmap) { - var blackBitmapSource = blackBitmap.Content as BitmapSource ?? throw new ArgumentException("Not a BitmapSource", nameof(blackBitmap)); - var whitkBitmapSource = whiteBitmap.Content as BitmapSource ?? throw new ArgumentException("Not a BitmapSource", nameof(whiteBitmap)); - var blackBuffer = new WriteableBitmap(blackBitmapSource); - var whiteBuffer = new WriteableBitmap(whitkBitmapSource); + var blackBuffer = new WriteableBitmap(blackBitmap.Content); + var whiteBuffer = new WriteableBitmap(whiteBitmap.Content); + var blackBitmapSource = blackBitmap.Content; var result = new WriteableBitmap((int)blackBitmapSource.Width, (int)blackBitmapSource.Height, blackBitmapSource.DpiX, blackBitmapSource.DpiY, PixelFormats.Bgra32, null); try @@ -458,7 +458,7 @@ namespace Greenshot.Core.Extensions blackBuffer.Unlock(); whiteBuffer.Unlock(); } - return new CaptureElement(blackBitmap.Bounds.Location, result); + return new CaptureElement(blackBitmap.Bounds.Location, result); } } } \ No newline at end of file diff --git a/src/Greenshot.Core/Interfaces/ICapture.cs b/src/Greenshot.Core/Interfaces/ICapture.cs index 4f4e1dab6..bcaa230ae 100644 --- a/src/Greenshot.Core/Interfaces/ICapture.cs +++ b/src/Greenshot.Core/Interfaces/ICapture.cs @@ -30,7 +30,7 @@ namespace Greenshot.Core.Interfaces /// /// This contains all the capture information /// - public interface ICapture + public interface ICapture : IDisposable { /// /// The calculated bounds for this capture @@ -55,6 +55,6 @@ namespace Greenshot.Core.Interfaces /// /// the actual capture elements, making up the capture /// - IList CaptureElements { get; } + IList> CaptureElements { get; } } } \ No newline at end of file diff --git a/src/Greenshot.Core/Interfaces/ICaptureElement.cs b/src/Greenshot.Core/Interfaces/ICaptureElement.cs index 6ac681efb..3095d3437 100644 --- a/src/Greenshot.Core/Interfaces/ICaptureElement.cs +++ b/src/Greenshot.Core/Interfaces/ICaptureElement.cs @@ -21,8 +21,8 @@ #endregion +using System; using System.Collections.Generic; -using System.Windows.Media; using Dapplo.Windows.Common.Structs; using Greenshot.Core.Enums; @@ -31,7 +31,7 @@ namespace Greenshot.Core.Interfaces /// /// This specifies a single element of a capture, making it possible to have a capture contain mouse, window, popup etc information /// - public interface ICaptureElement + public interface ICaptureElement : IDisposable { /// /// The location or bounds of the content @@ -45,7 +45,7 @@ namespace Greenshot.Core.Interfaces /// /// The actual content /// - ImageSource Content + TContent Content { get; set; diff --git a/src/Greenshot.Core/Interfaces/IDestination.cs b/src/Greenshot.Core/Interfaces/IDestination.cs index 361acd0b3..868e6f85b 100644 --- a/src/Greenshot.Core/Interfaces/IDestination.cs +++ b/src/Greenshot.Core/Interfaces/IDestination.cs @@ -29,7 +29,7 @@ namespace Greenshot.Core.Interfaces /// /// This IDestination describes things which can export a capture /// - public interface IDestination + public interface IDestination { /// /// This is called when the a capture needs to be exported @@ -37,6 +37,6 @@ namespace Greenshot.Core.Interfaces /// ICapture /// CancellationToken /// Task of bool - Task Export(ICapture capture, CancellationToken cancellationToken = default); + Task Export(ICapture capture, CancellationToken cancellationToken = default); } } diff --git a/src/Greenshot.Core/Interfaces/IProcessor.cs b/src/Greenshot.Core/Interfaces/IProcessor.cs index 4025d5534..b5eea06a6 100644 --- a/src/Greenshot.Core/Interfaces/IProcessor.cs +++ b/src/Greenshot.Core/Interfaces/IProcessor.cs @@ -30,7 +30,7 @@ namespace Greenshot.Core.Interfaces /// This IProcessor describes things which can process a capture /// An example would be to add a watermark to the capture /// - public interface IProcessor + public interface IProcessor { /// /// Process the capture @@ -38,6 +38,6 @@ namespace Greenshot.Core.Interfaces /// ICapture /// CancellationToken /// Task of bool - Task Process(ICapture capture, CancellationToken cancellationToken = default); + Task Process(ICapture capture, CancellationToken cancellationToken = default); } } diff --git a/src/Greenshot.Core/Interfaces/ISource.cs b/src/Greenshot.Core/Interfaces/ISource.cs index 13e3bf007..f18d1e367 100644 --- a/src/Greenshot.Core/Interfaces/ISource.cs +++ b/src/Greenshot.Core/Interfaces/ISource.cs @@ -30,13 +30,13 @@ namespace Greenshot.Core.Interfaces /// This interface defines sources, which can be used to get information from. /// For instance the screen, IE, a file etc. /// - public interface ISource + public interface ISource { /// /// Import an ICaptureElement from the source /// /// CancellationToken /// Task with ICaptureElement - ValueTask Import(CancellationToken cancellationToken = default); + ValueTask> Import(CancellationToken cancellationToken = default); } } diff --git a/src/Greenshot.Core/Interfaces/ITemplate.cs b/src/Greenshot.Core/Interfaces/ITemplate.cs index 88765c95c..9385d36a4 100644 --- a/src/Greenshot.Core/Interfaces/ITemplate.cs +++ b/src/Greenshot.Core/Interfaces/ITemplate.cs @@ -28,13 +28,13 @@ namespace Greenshot.Core.Interfaces /// /// This defines a template which is applied to a capture, so we can output it to the screen or to disk. /// - public interface ITemplate + public interface ITemplate { /// /// This applies a template, to generate a framework element /// /// ICapture /// FrameworkElement - FrameworkElement Apply(ICapture capture); + FrameworkElement Apply(ICapture capture); } } diff --git a/src/Greenshot.Core/Sources/DwmWindowSource.cs b/src/Greenshot.Core/Sources/DwmWindowSource.cs index 7a3086719..21d5ae708 100644 --- a/src/Greenshot.Core/Sources/DwmWindowSource.cs +++ b/src/Greenshot.Core/Sources/DwmWindowSource.cs @@ -24,6 +24,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using System.Windows.Media.Imaging; using Dapplo.Windows.Desktop; using Greenshot.Core.Configuration; using Greenshot.Core.Extensions; @@ -34,7 +35,7 @@ namespace Greenshot.Core.Sources /// /// This does the screen capture of a Window via DWM /// - public class DwmWindowSource : ISource + public class DwmWindowSource : ISource { private readonly ICaptureConfiguration _captureConfiguration; private readonly Func _retrieveWindowFunc; @@ -45,7 +46,7 @@ namespace Greenshot.Core.Sources _retrieveWindowFunc = retrieveWindowFunc ?? InteropWindowQuery.GetActiveWindow; } - public ValueTask Import(CancellationToken cancellationToken = default) + public ValueTask> Import(CancellationToken cancellationToken = default) { var window = _retrieveWindowFunc(); return window.CaptureDwmWindow(_captureConfiguration); diff --git a/src/Greenshot.Core/Sources/MouseSource.cs b/src/Greenshot.Core/Sources/MouseSource.cs index 65d5b2eef..5f8d106b5 100644 --- a/src/Greenshot.Core/Sources/MouseSource.cs +++ b/src/Greenshot.Core/Sources/MouseSource.cs @@ -23,6 +23,7 @@ using System.Threading; using System.Threading.Tasks; +using System.Windows.Media.Imaging; using Dapplo.Windows.Icons; using Greenshot.Core.Enums; using Greenshot.Core.Interfaces; @@ -32,21 +33,21 @@ namespace Greenshot.Core.Sources /// /// A source to capture the mouse cursor /// - public class MouseSource : ISource + public class MouseSource : ISource { /// - public ValueTask Import(CancellationToken cancellationToken = default) + public ValueTask> Import(CancellationToken cancellationToken = default) { - ICaptureElement result = null; + ICaptureElement result = null; if (CursorHelper.TryGetCurrentCursor(out var bitmapSource, out var location)) { - result = new CaptureElement(location, bitmapSource) + result = new CaptureElement(location, bitmapSource) { ElementType = CaptureElementType.Mouse }; } - return new ValueTask(result); + return new ValueTask>(result); } } diff --git a/src/Greenshot.Core/Sources/ScreenSource.cs b/src/Greenshot.Core/Sources/ScreenSource.cs index e512778b2..b524d7bf5 100644 --- a/src/Greenshot.Core/Sources/ScreenSource.cs +++ b/src/Greenshot.Core/Sources/ScreenSource.cs @@ -30,7 +30,6 @@ using System.Windows; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; -using Dapplo.Log; using Dapplo.Windows.Common; using Dapplo.Windows.Common.Extensions; using Dapplo.Windows.Common.Structs; @@ -49,14 +48,13 @@ namespace Greenshot.Core.Sources /// /// This does the screen capture /// - public class ScreenSource : ISource + public class ScreenSource : ISource { - private static readonly LogSource Log = new LogSource(); - public ValueTask Import(CancellationToken cancellationToken = default) + public ValueTask> Import(CancellationToken cancellationToken = default) { var screenbounds = DisplayInfo.GetAllScreenBounds(); var result = CaptureRectangle(screenbounds); - return new ValueTask(result); + return new ValueTask>(result); } /// @@ -83,9 +81,9 @@ namespace Greenshot.Core.Sources /// /// NativeRect /// ICaptureElement - internal static ICaptureElement CaptureRectangle(NativeRect captureBounds) + internal static ICaptureElement CaptureRectangle(NativeRect captureBounds) { - BitmapSource capturedBitmapSource = null; + BitmapSource capturedBitmapSource; if (captureBounds.IsEmpty) { return null; @@ -183,7 +181,7 @@ namespace Greenshot.Core.Sources } } } - ICaptureElement result = new CaptureElement(captureBounds.Location, capturedBitmapSource) + var result = new CaptureElement(captureBounds.Location, capturedBitmapSource) { ElementType = CaptureElementType.Screen }; diff --git a/src/Greenshot.Core/Templates/CroppedTemplate.cs b/src/Greenshot.Core/Templates/CroppedTemplate.cs index 496ac60ef..6813d3281 100644 --- a/src/Greenshot.Core/Templates/CroppedTemplate.cs +++ b/src/Greenshot.Core/Templates/CroppedTemplate.cs @@ -24,6 +24,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; +using System.Windows.Media.Imaging; using System.Windows.Shapes; using Greenshot.Core.Enums; using Greenshot.Core.Interfaces; @@ -33,10 +34,10 @@ namespace Greenshot.Core.Templates /// /// A template to create a FrameworkElement from a capture, with a crop applied /// - public class CroppedTemplate : ITemplate + public class CroppedTemplate : ITemplate { public bool DisplayMouse { get; set; } = true; - public FrameworkElement Apply(ICapture capture) + public FrameworkElement Apply(ICapture capture) { var width = (int)(capture.CropRect.Width + 0.5); var height = (int)(capture.CropRect.Height + 0.5); diff --git a/src/Greenshot.Core/Templates/SimpleTemplate.cs b/src/Greenshot.Core/Templates/SimpleTemplate.cs index 6099ea500..4e850dc1f 100644 --- a/src/Greenshot.Core/Templates/SimpleTemplate.cs +++ b/src/Greenshot.Core/Templates/SimpleTemplate.cs @@ -21,8 +21,10 @@ #endregion + using System.Windows; using System.Windows.Controls; +using System.Windows.Media.Imaging; using Greenshot.Core.Enums; using Greenshot.Core.Interfaces; @@ -31,10 +33,10 @@ namespace Greenshot.Core.Templates /// /// A template to create a FrameworkElement from a capture /// - public class SimpleTemplate : ITemplate + public class SimpleTemplate : ITemplate { public bool DisplayMouse { get; set; } = true; - public FrameworkElement Apply(ICapture capture) + public FrameworkElement Apply(ICapture capture) { var canvas = new Canvas { diff --git a/src/Greenshot.Tests/CaptureTests.cs b/src/Greenshot.Tests/CaptureTests.cs index 106220e45..b5b97176a 100644 --- a/src/Greenshot.Tests/CaptureTests.cs +++ b/src/Greenshot.Tests/CaptureTests.cs @@ -24,6 +24,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using System.Windows.Media.Imaging; using Dapplo.CaliburnMicro.Extensions; using Dapplo.Ini; using Dapplo.Windows.Desktop; @@ -48,7 +49,7 @@ namespace Greenshot.Tests [Fact] public async Task Test_CaptureFlow_ScreenSource() { - var captureFlow = new CaptureFlow + var captureFlow = new CaptureFlow { Sources = {new ScreenSource()} }; @@ -64,7 +65,7 @@ namespace Greenshot.Tests [Fact] public async Task Test_CaptureFlow_ScreenSource_MouseSource() { - var captureFlow = new CaptureFlow + var captureFlow = new CaptureFlow { Sources = { new ScreenSource() , new MouseSource()} }; @@ -82,16 +83,21 @@ namespace Greenshot.Tests { var iniConfig = new IniConfig("Greenshot.Tests", "Greenshot.Tests"); var config = iniConfig.Get(); - var captureFlow = new CaptureFlow + + var windowToCapture = InteropWindowQuery.GetTopLevelWindows().First(window => window.GetCaption().Contains("Notepad")); + var bounds = windowToCapture.GetInfo().Bounds; + var captureFlow = new CaptureFlow { - Sources = { new DwmWindowSource(config, () => InteropWindowQuery.GetTopLevelWindows().First(window => window.GetCaption().Contains("Notepad"))) } + Sources = { new DwmWindowSource(config, () => windowToCapture) } }; var capture = await captureFlow.Execute(); Assert.NotNull(capture); Assert.NotNull(capture.CaptureElements); var template = new SimpleTemplate(); - using (var outputStream = template.Apply(capture).ToBitmapSource().ToStream(OutputFormats.png)) + var bitmapSource = template.Apply(capture).ToBitmapSource(); + Assert.Equal(bounds.Size, bitmapSource.Size()); + using (var outputStream = bitmapSource.ToStream(OutputFormats.png)) using (var fileStream = File.Create("Test_CaptureFlow_DwmWindowSource.png")) { outputStream.Seek(0, SeekOrigin.Begin);