Some small fixes preventing NREs and wrote a capture test.

This commit is contained in:
Robin 2019-01-15 08:53:41 +01:00
commit cdf3428758
5 changed files with 85 additions and 23 deletions

View file

@ -28,26 +28,26 @@ using System.Drawing;
using System.Windows; using System.Windows;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Dapplo.Log;
using Dapplo.Windows.Common.Structs; using Dapplo.Windows.Common.Structs;
using Dapplo.Windows.Gdi32; using Dapplo.Windows.Gdi32;
using Dapplo.Windows.Gdi32.Enums; using Dapplo.Windows.Gdi32.Enums;
using Dapplo.Windows.Gdi32.SafeHandles; using Dapplo.Windows.Gdi32.SafeHandles;
using Dapplo.Windows.Gdi32.Structs; using Dapplo.Windows.Gdi32.Structs;
using Greenshot.Addons.Core; using Dapplo.Windows.User32;
#endregion #endregion
namespace Greenshot.PerformanceTests.Capture namespace Greenshot.Addons.Core
{ {
/// <summary> /// <summary>
/// The screen Capture code /// This allows us to repeatedly capture the screen via GDI
/// </summary> /// </summary>
public class ScreenCapture : IDisposable public class GdiScreenCapture : IDisposable
{ {
private readonly bool _useStretch;
private bool _hasFrame;
private readonly SafeWindowDcHandle _desktopDcHandle; private readonly SafeWindowDcHandle _desktopDcHandle;
private readonly SafeCompatibleDcHandle _safeCompatibleDcHandle; private readonly SafeCompatibleDcHandle _safeCompatibleDcHandle;
private readonly bool _useStretch;
private readonly SafeDibSectionHandle _safeDibSectionHandle; private readonly SafeDibSectionHandle _safeDibSectionHandle;
private readonly SafeSelectObjectHandle _safeSelectObjectHandle; private readonly SafeSelectObjectHandle _safeSelectObjectHandle;
@ -61,10 +61,16 @@ namespace Greenshot.PerformanceTests.Capture
/// </summary> /// </summary>
public NativeSize DestinationSize { get; } public NativeSize DestinationSize { get; }
public ScreenCapture(NativeRect sourceCaptureBounds, NativeSize? requestedSize = null) /// <summary>
/// The constructor
/// </summary>
/// <param name="sourceCaptureBounds">NativeRect, optional, with the source area from the screen</param>
/// <param name="requestedSize">NativeSize, optional, specifying the resulting size</param>
public GdiScreenCapture(NativeRect? sourceCaptureBounds = null, NativeSize? requestedSize = null)
{ {
SourceRect = sourceCaptureBounds; SourceRect = sourceCaptureBounds ?? DisplayInfo.ScreenBounds;
// Check if a size was specified, if this differs we need to stretch / scale
if (requestedSize.HasValue && requestedSize.Value != SourceRect.Size) if (requestedSize.HasValue && requestedSize.Value != SourceRect.Size)
{ {
DestinationSize = requestedSize.Value; DestinationSize = requestedSize.Value;
@ -76,22 +82,30 @@ namespace Greenshot.PerformanceTests.Capture
_useStretch = false; _useStretch = false;
} }
// Get a Device Context for the desktop
_desktopDcHandle = SafeWindowDcHandle.FromDesktop(); _desktopDcHandle = SafeWindowDcHandle.FromDesktop();
// Create a Device Context which is compatible with the desktop Device Context
_safeCompatibleDcHandle = Gdi32Api.CreateCompatibleDC(_desktopDcHandle); _safeCompatibleDcHandle = Gdi32Api.CreateCompatibleDC(_desktopDcHandle);
// Create BitmapInfoHeader for CreateDIBSection // Create BitmapInfoHeader, which is later used in the CreateDIBSection
var bitmapInfoHeader = BitmapInfoHeader.Create(DestinationSize.Width, DestinationSize.Height, 32); var bitmapInfoHeader = BitmapInfoHeader.Create(DestinationSize.Width, DestinationSize.Height, 32);
// Create a DibSection, a device-independent bitmap (DIB)
_safeDibSectionHandle = Gdi32Api.CreateDIBSection(_desktopDcHandle, ref bitmapInfoHeader, DibColors.RgbColors, out _, IntPtr.Zero, 0);
_safeDibSectionHandle = Gdi32Api.CreateDIBSection(_desktopDcHandle, ref bitmapInfoHeader, DibColors.PalColors, out _, IntPtr.Zero, 0); // select the device-independent bitmap in the device context, storing the previous.
// This is needed, so every interaction with the DC will go into the DIB.
// select the bitmap object and store the old handle
_safeSelectObjectHandle = _safeCompatibleDcHandle.SelectObject(_safeDibSectionHandle); _safeSelectObjectHandle = _safeCompatibleDcHandle.SelectObject(_safeDibSectionHandle);
} }
/// <summary>
/// Capture a frame from the screen
/// </summary>
public void CaptureFrame() public void CaptureFrame()
{ {
if (_useStretch) if (_useStretch)
{ {
// capture & blt over (make copy) // capture from source and blt over (make copy) to the DIB (via the DC)
// use stretching as the source and destination have different sizes
Gdi32Api.StretchBlt( Gdi32Api.StretchBlt(
_safeCompatibleDcHandle, 0, 0, DestinationSize.Width, DestinationSize.Height, // Destination _safeCompatibleDcHandle, 0, 0, DestinationSize.Width, DestinationSize.Height, // Destination
_desktopDcHandle, SourceRect.X, SourceRect.Y, SourceRect.Width, SourceRect.Height, // source _desktopDcHandle, SourceRect.X, SourceRect.Y, SourceRect.Width, SourceRect.Height, // source
@ -99,32 +113,45 @@ namespace Greenshot.PerformanceTests.Capture
} }
else else
{ {
// capture & blt over (make copy) // capture from source and blt over (make copy) to the DIB (via the DC)
Gdi32Api.BitBlt( Gdi32Api.BitBlt(
_safeCompatibleDcHandle, 0, 0, DestinationSize.Width, DestinationSize.Height, // Destination _safeCompatibleDcHandle, 0, 0, DestinationSize.Width, DestinationSize.Height, // Destination
_desktopDcHandle, SourceRect.X, SourceRect.Y, // Source _desktopDcHandle, SourceRect.X, SourceRect.Y, // Source
RasterOperations.SourceCopy | RasterOperations.CaptureBlt); RasterOperations.SourceCopy | RasterOperations.CaptureBlt);
} }
_hasFrame = true;
} }
/// <summary> /// <summary>
/// Get the current frame as BitmapSource /// Get the frame, captured with the previous CaptureFrame call, as BitmapSource
/// </summary> /// </summary>
/// <returns>BitmapSource</returns> /// <returns>BitmapSource</returns>
public BitmapSource CurrentFrameAsBitmapSource() public BitmapSource CurrentFrameAsBitmapSource()
{ {
if (!_hasFrame)
{
throw new NotSupportedException("No frame captured.");
}
return Imaging.CreateBitmapSourceFromHBitmap(_safeDibSectionHandle.DangerousGetHandle(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); return Imaging.CreateBitmapSourceFromHBitmap(_safeDibSectionHandle.DangerousGetHandle(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
} }
/// <summary> /// <summary>
/// Get the current frame as Bitmap /// Get the frame, captured with the previous CaptureFrame call, as
/// </summary> /// </summary>
/// <returns>Bitmap</returns> /// <returns>Bitmap</returns>
public Bitmap CurrentFrameAsBitmap() public Bitmap CurrentFrameAsBitmap()
{ {
if (!_hasFrame)
{
throw new NotSupportedException("No frame captured.");
}
return Image.FromHbitmap(_safeDibSectionHandle.DangerousGetHandle()); return Image.FromHbitmap(_safeDibSectionHandle.DangerousGetHandle());
} }
/// <summary>
/// Dispose all DC, DIB, handles etc
/// </summary>
public void Dispose() public void Dispose()
{ {
_safeSelectObjectHandle.Dispose(); _safeSelectObjectHandle.Dispose();

View file

@ -573,7 +573,7 @@ namespace Greenshot.Addons.Core
AddTag(bitmapToSave); AddTag(bitmapToSave);
// Added for OptiPNG // Added for OptiPNG
var processed = false; var processed = false;
if (Equals(imageFormat, ImageFormat.Png) && !string.IsNullOrEmpty(CoreConfiguration.OptimizePNGCommand)) if (Equals(imageFormat, ImageFormat.Png) && !string.IsNullOrEmpty(CoreConfiguration?.OptimizePNGCommand))
{ {
processed = ProcessPngImageExternally(bitmapToSave, targetStream); processed = ProcessPngImageExternally(bitmapToSave, targetStream);
} }

View file

@ -33,17 +33,24 @@ using Greenshot.Gfx.Effects;
namespace Greenshot.Addons.Interfaces.Plugin namespace Greenshot.Addons.Interfaces.Plugin
{ {
/// <summary>
/// This contains the settings for outputting a surface
/// </summary>
public class SurfaceOutputSettings public class SurfaceOutputSettings
{ {
private bool _disableReduceColors; private bool _disableReduceColors;
private bool _reduceColors; private bool _reduceColors;
/// <summary>
/// Constructor
/// </summary>
/// <param name="fileConfiguration">IFileConfiguration</param>
public SurfaceOutputSettings(IFileConfiguration fileConfiguration) public SurfaceOutputSettings(IFileConfiguration fileConfiguration)
{ {
_disableReduceColors = false; _disableReduceColors = false;
Format = fileConfiguration.OutputFileFormat; Format = fileConfiguration?.OutputFileFormat ?? OutputFormats.png;
JPGQuality = fileConfiguration.OutputFileJpegQuality; JPGQuality = fileConfiguration?.OutputFileJpegQuality ?? 80;
ReduceColors = fileConfiguration.OutputFileReduceColors; ReduceColors = fileConfiguration?.OutputFileReduceColors ?? false;
} }
public SurfaceOutputSettings(IFileConfiguration fileConfiguration, OutputFormats format) : this(fileConfiguration) public SurfaceOutputSettings(IFileConfiguration fileConfiguration, OutputFormats format) : this(fileConfiguration)

View file

@ -26,7 +26,6 @@ using BenchmarkDotNet.Attributes;
using Dapplo.Windows.Common.Structs; using Dapplo.Windows.Common.Structs;
using Dapplo.Windows.User32; using Dapplo.Windows.User32;
using Greenshot.Addons.Core; using Greenshot.Addons.Core;
using Greenshot.PerformanceTests.Capture;
namespace Greenshot.PerformanceTests namespace Greenshot.PerformanceTests
{ {
@ -37,9 +36,9 @@ namespace Greenshot.PerformanceTests
public class CapturePerformance public class CapturePerformance
{ {
// A ScreenCapture which captures the whole screen (multi-monitor) // A ScreenCapture which captures the whole screen (multi-monitor)
private readonly ScreenCapture _screenCapture = new ScreenCapture(DisplayInfo.ScreenBounds); private readonly GdiScreenCapture _screenCapture = new GdiScreenCapture(DisplayInfo.ScreenBounds);
// A ScreenCapture which captures the whole screen (multi-monitor) but with half the destination size, uses stretch-blt // A ScreenCapture which captures the whole screen (multi-monitor) but with half the destination size, uses stretch-blt
private readonly ScreenCapture _screenCaptureResized = new ScreenCapture(DisplayInfo.ScreenBounds, new NativeSize(DisplayInfo.ScreenBounds.Width / 2, DisplayInfo.ScreenBounds.Height / 2)); private readonly GdiScreenCapture _screenCaptureResized = new GdiScreenCapture(DisplayInfo.ScreenBounds, new NativeSize(DisplayInfo.ScreenBounds.Width / 2, DisplayInfo.ScreenBounds.Height / 2));
/// <summary> /// <summary>
/// This benchmarks a screen capture which does a lot of additional work /// This benchmarks a screen capture which does a lot of additional work

View file

@ -75,6 +75,35 @@ namespace Greenshot.Tests
Assert.Equal(2, capture.CaptureElements.Count); Assert.Equal(2, capture.CaptureElements.Count);
} }
/// <summary>
/// Test if multiple captures with GdiScreenCapture work
/// </summary>
[Fact]
public void Test_GdiScreenCapture()
{
using (var gdiScreenCapture = new GdiScreenCapture())
{
gdiScreenCapture.CaptureFrame();
using (var bitmap = gdiScreenCapture.CurrentFrameAsBitmap())
{
Assert.True(bitmap.Width > 0);
/*
// Write the capture to a file, for analysis
using (var stream = new FileStream(Path.Combine(Path.GetTempPath(), "test.png"), FileMode.Create, FileAccess.Write))
{
ImageOutput.SaveToStream(bitmap, null, stream, new SurfaceOutputSettings(null, OutputFormats.png));
}
*/
}
var bitmapSource = gdiScreenCapture.CurrentFrameAsBitmapSource();
Assert.True(bitmapSource.Width > 0);
gdiScreenCapture.CaptureFrame();
}
}
/// <summary> /// <summary>
/// Test if a capture from a window works /// Test if a capture from a window works
/// </summary> /// </summary>