Small changes to make the code more generic, so it's better possible to migrate.

This commit is contained in:
Robin 2018-08-21 17:23:26 +02:00
commit d8b58b1538
17 changed files with 119 additions and 66 deletions

View file

@ -31,13 +31,22 @@ using Greenshot.Core.Interfaces;
namespace Greenshot.Core
{
/// <inheritdoc />
public class Capture : ICapture
public class Capture<TContent> : ICapture<TContent>
{
public void Dispose()
{
foreach (var captureElement in CaptureElements)
{
captureElement?.Dispose();
}
CaptureElements.Clear();
}
/// <inheritdoc />
public DateTimeOffset Taken { get; } = DateTimeOffset.Now;
/// <inheritdoc />
public IList<ICaptureElement> CaptureElements { get; } = new List<ICaptureElement>();
public IList<ICaptureElement<TContent>> CaptureElements { get; } = new List<ICaptureElement<TContent>>();
/// <inheritdoc />
public NativeRect Bounds => CaptureElements.Select(element => element.Bounds).Aggregate((b1, b2) => b1.Union(b2));

View file

@ -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
/// <summary>
/// The CaptureElement contains the information of an element in a capture, e.g the window and mouse
/// </summary>
public class CaptureElement : ICaptureElement
public class CaptureElement<TContent> : ICaptureElement<TContent>
{
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; }
/// <inheritdoc />
public ImageSource Content { get; set; }
public TContent Content { get; set; }
/// <inheritdoc />
public CaptureElementType ElementType { get; set; } = CaptureElementType.Unknown;
/// <inheritdoc />
public IDictionary<string, string> MetaData { get; } = new Dictionary<string, string>();
public void Dispose()
{
if (Content is IDisposable disposable)
{
disposable.Dispose();
}
}
}
}

View file

@ -31,40 +31,40 @@ namespace Greenshot.Core
/// <summary>
/// This describes a capture flow, from source via processor to destination
/// </summary>
public class CaptureFlow
public class CaptureFlow<TContent>
{
/// <summary>
/// The ISource this capture flow contains
/// </summary>
public IList<ISource> Sources
public IList<ISource<TContent>> Sources
{
get;
} = new List<ISource>();
} = new List<ISource<TContent>>();
/// <summary>
/// The IProcessor this capture flow contains
/// </summary>
public IList<IProcessor> Processors
public IList<IProcessor<TContent>> Processors
{
get;
} = new List<IProcessor>();
} = new List<IProcessor<TContent>>();
/// <summary>
/// The IDestination this capture flow contains
/// </summary>
public IList<IDestination> Destinations
public IList<IDestination<TContent>> Destinations
{
get;
} = new List<IDestination>();
} = new List<IDestination<TContent>>();
/// <summary>
/// Execute this capture flow, to create a capture
/// </summary>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>ICapture</returns>
public async Task<ICapture> Execute(CancellationToken cancellationToken = default)
public async Task<ICapture<TContent>> Execute(CancellationToken cancellationToken = default)
{
var capture = new Capture();
var capture = new Capture<TContent>();
// Import the capture from the sources
foreach (var source in Sources)

View file

@ -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;
}
/// <summary>
/// Create a NativeSizeFloat object from a BitmapSource
/// </summary>
/// <param name="bitmapSource"></param>
/// <returns></returns>
public static NativeSizeFloat Size(this BitmapSource bitmapSource)
{
return new NativeSizeFloat(bitmapSource.Width, bitmapSource.Height);
}
}
}

View file

@ -71,10 +71,10 @@ namespace Greenshot.Core.Extensions
/// <param name="interopWindow">InteropWindow</param>
/// <param name="clientBounds">true to use the client bounds</param>
/// <returns>ICaptureElement</returns>
public static ICaptureElement CaptureFromScreen(this IInteropWindow interopWindow, bool clientBounds = false)
public static ICaptureElement<BitmapSource> CaptureFromScreen(this IInteropWindow interopWindow, bool clientBounds = false)
{
var bounds = clientBounds ? interopWindow.GetInfo().ClientBounds: interopWindow.GetInfo().Bounds;
ICaptureElement result = ScreenSource.CaptureRectangle(bounds);
ICaptureElement<BitmapSource> 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.
/// </summary>
/// <returns>ICaptureElement</returns>
public static ICaptureElement PrintWindow(this IInteropWindow interopWindow)
public static ICaptureElement<BitmapSource> PrintWindow(this IInteropWindow interopWindow)
{
var returnBitmap = interopWindow.PrintWindow<BitmapSource>();
if (interopWindow.HasParent || !interopWindow.IsMaximized())
{
return new CaptureElement(interopWindow.GetInfo().Bounds.Location, returnBitmap);
return new CaptureElement<BitmapSource>(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<BitmapSource>(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
/// </summary>
/// <param name="process">Process owning the window</param>
/// <param name="captureConfiguration">ICaptureConfiguration</param>
/// <returns>true if it's allowed</returns>
public static bool IsGdiAllowed(Process process, ICaptureConfiguration captureConfiguration)
{
@ -137,10 +138,10 @@ namespace Greenshot.Core.Extensions
/// <param name="interopWindow">IInteropWindow</param>
/// <param name="captureConfiguration">ICaptureConfiguration configuration for the settings</param>
/// <returns>ICaptureElement with the capture</returns>
public static async ValueTask<ICaptureElement> CaptureDwmWindow(this IInteropWindow interopWindow, ICaptureConfiguration captureConfiguration)
public static async ValueTask<ICaptureElement<BitmapSource>> CaptureDwmWindow(this IInteropWindow interopWindow, ICaptureConfiguration captureConfiguration)
{
// The capture
ICaptureElement capturedBitmap = null;
ICaptureElement<BitmapSource> capturedBitmap = null;
var thumbnailHandle = IntPtr.Zero;
Form tempForm = null;
var tempFormShown = false;
@ -387,12 +388,11 @@ namespace Greenshot.Core.Extensions
/// <param name="blackBitmap">ICaptureElement with the black image</param>
/// <param name="whiteBitmap">ICaptureElement with the white image</param>
/// <returns>ICaptureElement with transparency</returns>
private static ICaptureElement ApplyTransparency(ICaptureElement blackBitmap, ICaptureElement whiteBitmap)
private static ICaptureElement<BitmapSource> ApplyTransparency(ICaptureElement<BitmapSource> blackBitmap, ICaptureElement<BitmapSource> 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<BitmapSource>(blackBitmap.Bounds.Location, result);
}
}
}

View file

@ -30,7 +30,7 @@ namespace Greenshot.Core.Interfaces
/// <summary>
/// This contains all the capture information
/// </summary>
public interface ICapture
public interface ICapture<TContent> : IDisposable
{
/// <summary>
/// The calculated bounds for this capture
@ -55,6 +55,6 @@ namespace Greenshot.Core.Interfaces
/// <summary>
/// the actual capture elements, making up the capture
/// </summary>
IList<ICaptureElement> CaptureElements { get; }
IList<ICaptureElement<TContent>> CaptureElements { get; }
}
}

View file

@ -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
/// <summary>
/// This specifies a single element of a capture, making it possible to have a capture contain mouse, window, popup etc information
/// </summary>
public interface ICaptureElement
public interface ICaptureElement<TContent> : IDisposable
{
/// <summary>
/// The location or bounds of the content
@ -45,7 +45,7 @@ namespace Greenshot.Core.Interfaces
/// <summary>
/// The actual content
/// </summary>
ImageSource Content
TContent Content
{
get;
set;

View file

@ -29,7 +29,7 @@ namespace Greenshot.Core.Interfaces
/// <summary>
/// This IDestination describes things which can export a capture
/// </summary>
public interface IDestination
public interface IDestination<TContent>
{
/// <summary>
/// This is called when the a capture needs to be exported
@ -37,6 +37,6 @@ namespace Greenshot.Core.Interfaces
/// <param name="capture">ICapture</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>Task of bool</returns>
Task<bool> Export(ICapture capture, CancellationToken cancellationToken = default);
Task<bool> Export(ICapture<TContent> capture, CancellationToken cancellationToken = default);
}
}

View file

@ -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
/// </summary>
public interface IProcessor
public interface IProcessor<TContent>
{
/// <summary>
/// Process the capture
@ -38,6 +38,6 @@ namespace Greenshot.Core.Interfaces
/// <param name="capture">ICapture</param>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>Task of bool</returns>
Task<bool> Process(ICapture capture, CancellationToken cancellationToken = default);
Task<bool> Process(ICapture<TContent> capture, CancellationToken cancellationToken = default);
}
}

View file

@ -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.
/// </summary>
public interface ISource
public interface ISource<TResult>
{
/// <summary>
/// Import an ICaptureElement from the source
/// </summary>
/// <param name="cancellationToken">CancellationToken</param>
/// <returns>Task with ICaptureElement</returns>
ValueTask<ICaptureElement> Import(CancellationToken cancellationToken = default);
ValueTask<ICaptureElement<TResult>> Import(CancellationToken cancellationToken = default);
}
}

View file

@ -28,13 +28,13 @@ namespace Greenshot.Core.Interfaces
/// <summary>
/// This defines a template which is applied to a capture, so we can output it to the screen or to disk.
/// </summary>
public interface ITemplate
public interface ITemplate<TContent>
{
/// <summary>
/// This applies a template, to generate a framework element
/// </summary>
/// <param name="capture">ICapture</param>
/// <returns>FrameworkElement</returns>
FrameworkElement Apply(ICapture capture);
FrameworkElement Apply(ICapture<TContent> capture);
}
}

View file

@ -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
/// <summary>
/// This does the screen capture of a Window via DWM
/// </summary>
public class DwmWindowSource : ISource
public class DwmWindowSource : ISource<BitmapSource>
{
private readonly ICaptureConfiguration _captureConfiguration;
private readonly Func<IInteropWindow> _retrieveWindowFunc;
@ -45,7 +46,7 @@ namespace Greenshot.Core.Sources
_retrieveWindowFunc = retrieveWindowFunc ?? InteropWindowQuery.GetActiveWindow;
}
public ValueTask<ICaptureElement> Import(CancellationToken cancellationToken = default)
public ValueTask<ICaptureElement<BitmapSource>> Import(CancellationToken cancellationToken = default)
{
var window = _retrieveWindowFunc();
return window.CaptureDwmWindow(_captureConfiguration);

View file

@ -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
/// <summary>
/// A source to capture the mouse cursor
/// </summary>
public class MouseSource : ISource
public class MouseSource : ISource<BitmapSource>
{
/// <inheritdoc />
public ValueTask<ICaptureElement> Import(CancellationToken cancellationToken = default)
public ValueTask<ICaptureElement<BitmapSource>> Import(CancellationToken cancellationToken = default)
{
ICaptureElement result = null;
ICaptureElement<BitmapSource> result = null;
if (CursorHelper.TryGetCurrentCursor(out var bitmapSource, out var location))
{
result = new CaptureElement(location, bitmapSource)
result = new CaptureElement<BitmapSource>(location, bitmapSource)
{
ElementType = CaptureElementType.Mouse
};
}
return new ValueTask<ICaptureElement>(result);
return new ValueTask<ICaptureElement<BitmapSource>>(result);
}
}

View file

@ -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
/// <summary>
/// This does the screen capture
/// </summary>
public class ScreenSource : ISource
public class ScreenSource : ISource<BitmapSource>
{
private static readonly LogSource Log = new LogSource();
public ValueTask<ICaptureElement> Import(CancellationToken cancellationToken = default)
public ValueTask<ICaptureElement<BitmapSource>> Import(CancellationToken cancellationToken = default)
{
var screenbounds = DisplayInfo.GetAllScreenBounds();
var result = CaptureRectangle(screenbounds);
return new ValueTask<ICaptureElement>(result);
return new ValueTask<ICaptureElement<BitmapSource>>(result);
}
/// <summary>
@ -83,9 +81,9 @@ namespace Greenshot.Core.Sources
/// </summary>
/// <param name="captureBounds">NativeRect</param>
/// <returns>ICaptureElement</returns>
internal static ICaptureElement CaptureRectangle(NativeRect captureBounds)
internal static ICaptureElement<BitmapSource> 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<BitmapSource>(captureBounds.Location, capturedBitmapSource)
{
ElementType = CaptureElementType.Screen
};

View file

@ -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
/// <summary>
/// A template to create a FrameworkElement from a capture, with a crop applied
/// </summary>
public class CroppedTemplate : ITemplate
public class CroppedTemplate : ITemplate<BitmapSource>
{
public bool DisplayMouse { get; set; } = true;
public FrameworkElement Apply(ICapture capture)
public FrameworkElement Apply(ICapture<BitmapSource> capture)
{
var width = (int)(capture.CropRect.Width + 0.5);
var height = (int)(capture.CropRect.Height + 0.5);

View file

@ -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
/// <summary>
/// A template to create a FrameworkElement from a capture
/// </summary>
public class SimpleTemplate : ITemplate
public class SimpleTemplate : ITemplate<BitmapSource>
{
public bool DisplayMouse { get; set; } = true;
public FrameworkElement Apply(ICapture capture)
public FrameworkElement Apply(ICapture<BitmapSource> capture)
{
var canvas = new Canvas
{

View file

@ -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<BitmapSource>
{
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<BitmapSource>
{
Sources = { new ScreenSource() , new MouseSource()}
};
@ -82,16 +83,21 @@ namespace Greenshot.Tests
{
var iniConfig = new IniConfig("Greenshot.Tests", "Greenshot.Tests");
var config = iniConfig.Get<ICoreConfiguration>();
var captureFlow = new CaptureFlow
var windowToCapture = InteropWindowQuery.GetTopLevelWindows().First(window => window.GetCaption().Contains("Notepad"));
var bounds = windowToCapture.GetInfo().Bounds;
var captureFlow = new CaptureFlow<BitmapSource>
{
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);