From cdf34287589a8c7317b52c6e359387ac7f8d0ff4 Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 15 Jan 2019 08:53:41 +0100 Subject: [PATCH] Some small fixes preventing NREs and wrote a capture test. --- .../Core/GdiScreenCapture.cs} | 59 ++++++++++++++----- src/Greenshot.Addons/Core/ImageOutput.cs | 2 +- .../Plugin/SurfaceOutputSettings.cs | 13 +++- .../CapturePerformance.cs | 5 +- src/Greenshot.Tests/CaptureTests.cs | 29 +++++++++ 5 files changed, 85 insertions(+), 23 deletions(-) rename src/{Greenshot.PerformanceTests/Capture/ScreenCapture.cs => Greenshot.Addons/Core/GdiScreenCapture.cs} (64%) diff --git a/src/Greenshot.PerformanceTests/Capture/ScreenCapture.cs b/src/Greenshot.Addons/Core/GdiScreenCapture.cs similarity index 64% rename from src/Greenshot.PerformanceTests/Capture/ScreenCapture.cs rename to src/Greenshot.Addons/Core/GdiScreenCapture.cs index ae1c3d080..46aaa03ed 100644 --- a/src/Greenshot.PerformanceTests/Capture/ScreenCapture.cs +++ b/src/Greenshot.Addons/Core/GdiScreenCapture.cs @@ -28,26 +28,26 @@ using System.Drawing; using System.Windows; using System.Windows.Interop; using System.Windows.Media.Imaging; -using Dapplo.Log; using Dapplo.Windows.Common.Structs; using Dapplo.Windows.Gdi32; using Dapplo.Windows.Gdi32.Enums; using Dapplo.Windows.Gdi32.SafeHandles; using Dapplo.Windows.Gdi32.Structs; -using Greenshot.Addons.Core; +using Dapplo.Windows.User32; #endregion -namespace Greenshot.PerformanceTests.Capture +namespace Greenshot.Addons.Core { /// - /// The screen Capture code + /// This allows us to repeatedly capture the screen via GDI /// - public class ScreenCapture : IDisposable + public class GdiScreenCapture : IDisposable { + private readonly bool _useStretch; + private bool _hasFrame; private readonly SafeWindowDcHandle _desktopDcHandle; private readonly SafeCompatibleDcHandle _safeCompatibleDcHandle; - private readonly bool _useStretch; private readonly SafeDibSectionHandle _safeDibSectionHandle; private readonly SafeSelectObjectHandle _safeSelectObjectHandle; @@ -61,10 +61,16 @@ namespace Greenshot.PerformanceTests.Capture /// public NativeSize DestinationSize { get; } - public ScreenCapture(NativeRect sourceCaptureBounds, NativeSize? requestedSize = null) + /// + /// The constructor + /// + /// NativeRect, optional, with the source area from the screen + /// NativeSize, optional, specifying the resulting size + 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) { DestinationSize = requestedSize.Value; @@ -76,22 +82,30 @@ namespace Greenshot.PerformanceTests.Capture _useStretch = false; } + // Get a Device Context for the desktop _desktopDcHandle = SafeWindowDcHandle.FromDesktop(); + + // Create a Device Context which is compatible with the desktop Device Context _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); + // 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 bitmap object and store the old handle + // 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. _safeSelectObjectHandle = _safeCompatibleDcHandle.SelectObject(_safeDibSectionHandle); } + /// + /// Capture a frame from the screen + /// public void CaptureFrame() { 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( _safeCompatibleDcHandle, 0, 0, DestinationSize.Width, DestinationSize.Height, // Destination _desktopDcHandle, SourceRect.X, SourceRect.Y, SourceRect.Width, SourceRect.Height, // source @@ -99,32 +113,45 @@ namespace Greenshot.PerformanceTests.Capture } else { - // capture & blt over (make copy) + // capture from source and blt over (make copy) to the DIB (via the DC) Gdi32Api.BitBlt( _safeCompatibleDcHandle, 0, 0, DestinationSize.Width, DestinationSize.Height, // Destination _desktopDcHandle, SourceRect.X, SourceRect.Y, // Source RasterOperations.SourceCopy | RasterOperations.CaptureBlt); } + + _hasFrame = true; } /// - /// Get the current frame as BitmapSource + /// Get the frame, captured with the previous CaptureFrame call, as BitmapSource /// /// BitmapSource public BitmapSource CurrentFrameAsBitmapSource() { + if (!_hasFrame) + { + throw new NotSupportedException("No frame captured."); + } return Imaging.CreateBitmapSourceFromHBitmap(_safeDibSectionHandle.DangerousGetHandle(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); } /// - /// Get the current frame as Bitmap + /// Get the frame, captured with the previous CaptureFrame call, as /// /// Bitmap public Bitmap CurrentFrameAsBitmap() { + if (!_hasFrame) + { + throw new NotSupportedException("No frame captured."); + } return Image.FromHbitmap(_safeDibSectionHandle.DangerousGetHandle()); } + /// + /// Dispose all DC, DIB, handles etc + /// public void Dispose() { _safeSelectObjectHandle.Dispose(); diff --git a/src/Greenshot.Addons/Core/ImageOutput.cs b/src/Greenshot.Addons/Core/ImageOutput.cs index 7a01ae02f..360fb4383 100644 --- a/src/Greenshot.Addons/Core/ImageOutput.cs +++ b/src/Greenshot.Addons/Core/ImageOutput.cs @@ -573,7 +573,7 @@ namespace Greenshot.Addons.Core AddTag(bitmapToSave); // Added for OptiPNG 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); } diff --git a/src/Greenshot.Addons/Interfaces/Plugin/SurfaceOutputSettings.cs b/src/Greenshot.Addons/Interfaces/Plugin/SurfaceOutputSettings.cs index b06c53945..ef312d4f2 100644 --- a/src/Greenshot.Addons/Interfaces/Plugin/SurfaceOutputSettings.cs +++ b/src/Greenshot.Addons/Interfaces/Plugin/SurfaceOutputSettings.cs @@ -33,17 +33,24 @@ using Greenshot.Gfx.Effects; namespace Greenshot.Addons.Interfaces.Plugin { + /// + /// This contains the settings for outputting a surface + /// public class SurfaceOutputSettings { private bool _disableReduceColors; private bool _reduceColors; + /// + /// Constructor + /// + /// IFileConfiguration public SurfaceOutputSettings(IFileConfiguration fileConfiguration) { _disableReduceColors = false; - Format = fileConfiguration.OutputFileFormat; - JPGQuality = fileConfiguration.OutputFileJpegQuality; - ReduceColors = fileConfiguration.OutputFileReduceColors; + Format = fileConfiguration?.OutputFileFormat ?? OutputFormats.png; + JPGQuality = fileConfiguration?.OutputFileJpegQuality ?? 80; + ReduceColors = fileConfiguration?.OutputFileReduceColors ?? false; } public SurfaceOutputSettings(IFileConfiguration fileConfiguration, OutputFormats format) : this(fileConfiguration) diff --git a/src/Greenshot.PerformanceTests/CapturePerformance.cs b/src/Greenshot.PerformanceTests/CapturePerformance.cs index ef341917b..82e8c6a3b 100644 --- a/src/Greenshot.PerformanceTests/CapturePerformance.cs +++ b/src/Greenshot.PerformanceTests/CapturePerformance.cs @@ -26,7 +26,6 @@ using BenchmarkDotNet.Attributes; using Dapplo.Windows.Common.Structs; using Dapplo.Windows.User32; using Greenshot.Addons.Core; -using Greenshot.PerformanceTests.Capture; namespace Greenshot.PerformanceTests { @@ -37,9 +36,9 @@ namespace Greenshot.PerformanceTests public class CapturePerformance { // 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 - 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)); /// /// This benchmarks a screen capture which does a lot of additional work diff --git a/src/Greenshot.Tests/CaptureTests.cs b/src/Greenshot.Tests/CaptureTests.cs index bbca527c5..d58e74073 100644 --- a/src/Greenshot.Tests/CaptureTests.cs +++ b/src/Greenshot.Tests/CaptureTests.cs @@ -75,6 +75,35 @@ namespace Greenshot.Tests Assert.Equal(2, capture.CaptureElements.Count); } + /// + /// Test if multiple captures with GdiScreenCapture work + /// + [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(); + } + } + /// /// Test if a capture from a window works ///