From ee779cc97bdd05c1b6082a1f1009284ab23a6bdc Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 28 Mar 2019 13:42:31 +0100 Subject: [PATCH] Improved the UnmanagedBitmap, and all IBitmapWithNativeSupport to support both Bitmap and BitmapSource with minimal copying. This should make integrations of newer code a lot easier. --- src/Directory.Build.props | 6 +- .../Core/BitmapScreenCapture.cs | 138 ++++++++++++++++++ src/Greenshot.Addons/Core/WindowCapture.cs | 15 +- src/Greenshot.Gfx/BitmapWrapper.cs | 22 ++- .../FastBitmap/FastBitmapFactory.cs | 3 +- src/Greenshot.Gfx/IBitmap.cs | 9 +- src/Greenshot.Gfx/Quantizer/WuQuantizer.cs | 9 +- src/Greenshot.Gfx/SvgBitmap.cs | 27 +++- src/Greenshot.Gfx/UnmanagedBitmap.cs | 68 ++++++++- .../CapturePerformance.cs | 32 +++- .../GfxPerformance.cs | 2 +- .../GfxPerformanceShort.cs | 31 ---- .../Greenshot.PerformanceTests.csproj | 3 - src/Greenshot.PerformanceTests/Program.cs | 3 +- src/Greenshot.Tests/CaptureTests.cs | 30 ++++ 15 files changed, 330 insertions(+), 68 deletions(-) create mode 100644 src/Greenshot.Addons/Core/BitmapScreenCapture.cs delete mode 100644 src/Greenshot.PerformanceTests/GfxPerformanceShort.cs diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e10935e7c..aabba5714 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -48,8 +48,8 @@ True - - + NativeRect, optional, with the source area from the screen + /// NativeSize, optional, specifying the resulting size + public BitmapScreenCapture(NativeRect? sourceCaptureBounds = null, NativeSize? requestedSize = null) + { + 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; + _useStretch = true; + } + else + { + DestinationSize = SourceRect.Size; + _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, 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 var bits, 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. + _safeSelectObjectHandle = _safeCompatibleDcHandle.SelectObject(_safeDibSectionHandle); + + // Create a wrapper around the bitmap data + _bitmap = new UnmanagedBitmap(bits, DestinationSize.Width, DestinationSize.Height); + } + + /// + /// Capture a frame from the screen + /// + public void CaptureFrame() + { + if (_useStretch) + { + // 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 + RasterOperations.SourceCopy | RasterOperations.CaptureBlt); + } + else + { + // 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 frame, captured with the previous CaptureFrame call + /// + /// IBitmapWithNativeSupport + public IBitmapWithNativeSupport CurrentFrameAsBitmap() => _hasFrame ? _bitmap : null; + + /// + /// Dispose all DC, DIB, handles etc + /// + public void Dispose() + { + _safeSelectObjectHandle.Dispose(); + _safeDibSectionHandle.Dispose(); + _safeCompatibleDcHandle.Dispose(); + _desktopDcHandle.Dispose(); + } + + } +} \ No newline at end of file diff --git a/src/Greenshot.Addons/Core/WindowCapture.cs b/src/Greenshot.Addons/Core/WindowCapture.cs index f748b00ae..451c75219 100644 --- a/src/Greenshot.Addons/Core/WindowCapture.cs +++ b/src/Greenshot.Addons/Core/WindowCapture.cs @@ -346,8 +346,7 @@ namespace Greenshot.Addons.Core } // get a .NET image object for it - // A suggestion for the "A generic error occurred in GDI+." E_FAIL/0�80004005 error is to re-try... - var success = false; + // A suggestion for the "A generic error occurred in GDI+." E_FAIL/0x80004005 error is to re-try... ExternalException exception = null; for (var i = 0; i < 3; i++) { @@ -408,9 +407,6 @@ namespace Greenshot.Addons.Core // TODO: Optimize? return BitmapWrapper.FromBitmap(Image.FromHbitmap(safeDibSectionHandle.DangerousGetHandle())); } - // We got through the capture without exception - success = true; - break; } catch (ExternalException ee) { @@ -418,13 +414,10 @@ namespace Greenshot.Addons.Core exception = ee; } } - if (!success) + Log.Error().WriteLine(null, "Still couldn't create Bitmap!"); + if (exception != null) { - Log.Error().WriteLine(null, "Still couldn't create Bitmap!"); - if (exception != null) - { - throw exception; - } + throw exception; } } } diff --git a/src/Greenshot.Gfx/BitmapWrapper.cs b/src/Greenshot.Gfx/BitmapWrapper.cs index 5d991a0a6..f3c11ad34 100644 --- a/src/Greenshot.Gfx/BitmapWrapper.cs +++ b/src/Greenshot.Gfx/BitmapWrapper.cs @@ -19,6 +19,8 @@ using System.Drawing; using System.Drawing.Imaging; +using System.Windows.Media; +using System.Windows.Media.Imaging; namespace Greenshot.Gfx { @@ -49,7 +51,7 @@ namespace Greenshot.Gfx public int Width => _bitmap.Width; /// - public PixelFormat PixelFormat => _bitmap.PixelFormat; + public System.Drawing.Imaging.PixelFormat PixelFormat => _bitmap.PixelFormat; /// public float HorizontalResolution => _bitmap.HorizontalResolution; @@ -60,6 +62,24 @@ namespace Greenshot.Gfx /// public Bitmap NativeBitmap => _bitmap; + /// + public BitmapSource NativeBitmapSource + { + get + { + var bitmapData = _bitmap.LockBits(new Rectangle(0, 0, _bitmap.Width, _bitmap.Height), ImageLockMode.ReadOnly, _bitmap.PixelFormat); + try + { + return BitmapSource.Create(bitmapData.Width, bitmapData.Height, _bitmap.HorizontalResolution, _bitmap.VerticalResolution, PixelFormats.Bgr24, null, bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride); + } + finally + { + _bitmap.UnlockBits(bitmapData); + } + } + } + + /// public Size Size => new Size(Width, Height); /// diff --git a/src/Greenshot.Gfx/FastBitmap/FastBitmapFactory.cs b/src/Greenshot.Gfx/FastBitmap/FastBitmapFactory.cs index 76e1aef83..2db885623 100644 --- a/src/Greenshot.Gfx/FastBitmap/FastBitmapFactory.cs +++ b/src/Greenshot.Gfx/FastBitmap/FastBitmapFactory.cs @@ -87,8 +87,7 @@ namespace Greenshot.Gfx.FastBitmap /// float for horizontal DPI /// float for horizontal DPI /// IFastBitmap - public static IFastBitmap CreateEmpty(Size newSize, PixelFormat pixelFormat = PixelFormat.DontCare, Color? backgroundColor = null, float horizontalResolution = 96f, - float verticalResolution = 96f) + public static IFastBitmap CreateEmpty(Size newSize, PixelFormat pixelFormat = PixelFormat.DontCare, Color? backgroundColor = null, float horizontalResolution = 96f, float verticalResolution = 96f) { var destination = BitmapFactory.CreateEmpty(newSize.Width, newSize.Height, pixelFormat, backgroundColor, horizontalResolution, verticalResolution); var fastBitmap = Create(destination); diff --git a/src/Greenshot.Gfx/IBitmap.cs b/src/Greenshot.Gfx/IBitmap.cs index 78e11c4ac..0916cfa03 100644 --- a/src/Greenshot.Gfx/IBitmap.cs +++ b/src/Greenshot.Gfx/IBitmap.cs @@ -20,6 +20,7 @@ using System; using System.Drawing; using System.Drawing.Imaging; +using System.Windows.Media.Imaging; namespace Greenshot.Gfx { @@ -61,9 +62,15 @@ namespace Greenshot.Gfx public interface IBitmapWithNativeSupport : IBitmap { /// - /// Underlying image, or an on demand rendered version with different attributes as the original + /// Retrieves a Bitmap which only can be used as long as the underlying implementation is not disposed. + /// Do not dispose this. /// Bitmap NativeBitmap { get; } + + /// + /// Retrieves a BitmapSource which only can be used as long as the underlying implementation is not disposed. + /// + BitmapSource NativeBitmapSource { get; } /// /// Return the Size diff --git a/src/Greenshot.Gfx/Quantizer/WuQuantizer.cs b/src/Greenshot.Gfx/Quantizer/WuQuantizer.cs index 0aa9a1cfe..847905408 100644 --- a/src/Greenshot.Gfx/Quantizer/WuQuantizer.cs +++ b/src/Greenshot.Gfx/Quantizer/WuQuantizer.cs @@ -149,12 +149,17 @@ namespace Greenshot.Gfx.Quantizer } } } - + /// public void Dispose() { Dispose(true); } + + /// + /// Dispose implementation + /// + /// bool protected virtual void Dispose(bool disposing) { if (!disposing) @@ -308,7 +313,7 @@ namespace Greenshot.Gfx.Quantizer _tag = new byte[Maxvolume]; - // precalculates lookup tables + // pre-calculates lookup tables for (var k = 0; k < allowedColorCount; ++k) { Mark(_cubes[k], k, _tag); diff --git a/src/Greenshot.Gfx/SvgBitmap.cs b/src/Greenshot.Gfx/SvgBitmap.cs index f9d9930ce..fab793b58 100644 --- a/src/Greenshot.Gfx/SvgBitmap.cs +++ b/src/Greenshot.Gfx/SvgBitmap.cs @@ -20,7 +20,11 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; using Svg; +using Color = System.Drawing.Color; +using PixelFormat = System.Drawing.Imaging.PixelFormat; namespace Greenshot.Gfx @@ -111,6 +115,24 @@ namespace Greenshot.Gfx /// public Bitmap NativeBitmap => GenerateNativeBitmap(); + /// + public BitmapSource NativeBitmapSource + { + get + { + GenerateNativeBitmap(); + var bitmapData = _imageClone.LockBits(new Rectangle(0, 0, _imageClone.Width, _imageClone.Height), ImageLockMode.ReadOnly, _imageClone.PixelFormat); + try + { + return BitmapSource.Create(bitmapData.Width, bitmapData.Height, _imageClone.HorizontalResolution, _imageClone.VerticalResolution, PixelFormats.Bgr24, null, bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride); + } + finally + { + _imageClone.UnlockBits(bitmapData); + } + } + } + private Bitmap GenerateNativeBitmap() { if (_imageClone?.Height == Height && _imageClone?.Width == Width) @@ -128,11 +150,6 @@ namespace Greenshot.Gfx public Size Size => new Size(Width, Height); - public void DisposeNativeBitmap(Bitmap nativeBitmap) - { - // do nothing, we need this - } - /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/src/Greenshot.Gfx/UnmanagedBitmap.cs b/src/Greenshot.Gfx/UnmanagedBitmap.cs index 9b391044b..6a5e9df63 100644 --- a/src/Greenshot.Gfx/UnmanagedBitmap.cs +++ b/src/Greenshot.Gfx/UnmanagedBitmap.cs @@ -19,10 +19,12 @@ using System; using System.Drawing; -using System.Drawing.Imaging; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Windows.Media; +using System.Windows.Media.Imaging; using Greenshot.Gfx.Structs; +using PixelFormat = System.Drawing.Imaging.PixelFormat; namespace Greenshot.Gfx { @@ -34,8 +36,13 @@ namespace Greenshot.Gfx { private readonly float _horizontalPixelsPerInch; private readonly float _verticalPixelsPerInch; + // Bytes per line private readonly int _stride; + // IntPtr to a global handle with the bitmap data, this will be freed on dispose private IntPtr _hGlobal; + // IntPtr to the bits of the bitmap, if using the constructor with the IntPtr this will not be freed + private readonly IntPtr _bits; + // Optionally created when the user calls NativeBitmap private Bitmap _nativeBitmap; /// @@ -64,10 +71,30 @@ namespace Greenshot.Gfx Height = height; _stride = bytesPerPixel * width; var bytesAllocated = height * _stride; - _hGlobal = Marshal.AllocHGlobal(bytesAllocated); + _bits = _hGlobal = Marshal.AllocHGlobal(bytesAllocated); GC.AddMemoryPressure(bytesAllocated); } + + /// + /// The constructor for the UnmanagedBitmap with already initialized bits + /// + /// IntPtr to the bits, this will not be freed + /// int + /// int + /// float + /// float + public UnmanagedBitmap(IntPtr bits, int width, int height, float horizontalPixelsPerInch = 0.96f, float verticalPixelsPerInch = 0.96f) + { + _horizontalPixelsPerInch = horizontalPixelsPerInch; + _verticalPixelsPerInch = verticalPixelsPerInch; + var bytesPerPixel = Marshal.SizeOf(); + Width = width; + Height = height; + _stride = bytesPerPixel * width; + _bits = bits; + } + /// /// This returns a span with a the pixels for the specified line /// @@ -79,7 +106,7 @@ namespace Greenshot.Gfx { unsafe { - var pixelRowPointer = _hGlobal + (y * _stride); + var pixelRowPointer = _bits + (y * _stride); return new Span(pixelRowPointer.ToPointer(), Width); } } @@ -96,7 +123,7 @@ namespace Greenshot.Gfx { unsafe { - return new Span(_hGlobal.ToPointer(), Height * Width); + return new Span(_bits.ToPointer(), Height * Width); } } } @@ -126,6 +153,25 @@ namespace Greenshot.Gfx return format; } } + + public System.Windows.Media.PixelFormat WpfPixelFormat + { + get + { + TPixelLayout empty = default; + switch (empty) + { + case Bgr24 _: + return PixelFormats.Bgr24; + case Bgra32 _: + return PixelFormats.Bgra32; + case Bgr32 _: + return PixelFormats.Bgr32; + default: + throw new NotSupportedException("Unknown pixel format"); + } + } + } /// public float VerticalResolution => _verticalPixelsPerInch; @@ -141,7 +187,19 @@ namespace Greenshot.Gfx { get { - return _nativeBitmap ??= new Bitmap(Width, Height, _stride, PixelFormat, _hGlobal); + return _nativeBitmap ??= new Bitmap(Width, Height, _stride, PixelFormat, _bits); + } + } + + /// + /// Convert this to a real bitmap + /// + /// BitmapSource + public BitmapSource NativeBitmapSource + { + get + { + return BitmapSource.Create(Width, Height, HorizontalResolution, VerticalResolution, WpfPixelFormat, null, _bits, _stride * Height, _stride); } } diff --git a/src/Greenshot.PerformanceTests/CapturePerformance.cs b/src/Greenshot.PerformanceTests/CapturePerformance.cs index af5cf8bda..969067f3e 100644 --- a/src/Greenshot.PerformanceTests/CapturePerformance.cs +++ b/src/Greenshot.PerformanceTests/CapturePerformance.cs @@ -18,6 +18,7 @@ // along with this program. If not, see . using System; +using System.Drawing.Imaging; using System.IO; using BenchmarkDotNet.Attributes; using Dapplo.Log; @@ -32,7 +33,7 @@ namespace Greenshot.PerformanceTests /// /// This defines the benchmarks which can be done /// - [MinColumn, MaxColumn, MemoryDiagnoser] + [MinColumn, MaxColumn, MemoryDiagnoser, CoreJob, ClrJob] public class CapturePerformance { private static readonly LogSource Log = new LogSource(); @@ -40,6 +41,8 @@ namespace Greenshot.PerformanceTests private GdiScreenCapture _screenCapture; // A ScreenCapture which captures the whole screen (multi-monitor) but with half the destination size, uses stretch-blt private GdiScreenCapture _screenCaptureResized; + private BitmapScreenCapture _screenBitmapCapture; + private BitmapScreenCapture _screenBitmapCaptureResized; private AviWriter _aviWriter; private IAviVideoStream _aviVideoStream; @@ -47,8 +50,10 @@ namespace Greenshot.PerformanceTests public void Setup() { _screenCapture = new GdiScreenCapture(DisplayInfo.ScreenBounds); + _screenBitmapCapture = new BitmapScreenCapture(); var resizedSize = new NativeSize(DisplayInfo.ScreenBounds.Width / 2, DisplayInfo.ScreenBounds.Height / 2); _screenCaptureResized = new GdiScreenCapture(DisplayInfo.ScreenBounds, resizedSize); + _screenBitmapCaptureResized = new BitmapScreenCapture(DisplayInfo.ScreenBounds, resizedSize); var aviFile = Path.Combine(Path.GetTempPath(), @"test.avi"); Log.Info().WriteLine("Writing AVI to {0}", aviFile); @@ -61,12 +66,13 @@ namespace Greenshot.PerformanceTests EmitIndex1 = true }; _aviVideoStream = _aviWriter.AddVideoStream(resizedSize.Width, resizedSize.Height, BitsPerPixel.Bpp24); + } /// /// This benchmarks a screen capture which does a lot of additional work /// - [Benchmark] + //[Benchmark] public void Capture() { using (var capture = WindowCapture.CaptureScreen()) @@ -90,6 +96,15 @@ namespace Greenshot.PerformanceTests { _screenCapture.CaptureFrame(); } + + /// + /// Capture the screen directly into a bitmap + /// + [Benchmark] + public void CaptureBitmap() + { + _screenBitmapCapture.CaptureFrame(); + } /// /// Capture the screen with buffered settings, but resized (smaller) destination @@ -99,6 +114,17 @@ namespace Greenshot.PerformanceTests { _screenCaptureResized.CaptureFrame(); } + + + /// + /// Capture the screen with buffered settings, but resized (smaller) destination + /// + //[Benchmark] + public void CapturebitmapResized() + { + _screenBitmapCaptureResized.CaptureFrame(); + } + /// /// Capture the screen with buffered settings, but resized (smaller) destination @@ -115,6 +141,8 @@ namespace Greenshot.PerformanceTests public void Cleanup() { _screenCapture.Dispose(); + _screenBitmapCapture.Dispose(); + _screenBitmapCaptureResized.Dispose(); _aviWriter.Close(); } } diff --git a/src/Greenshot.PerformanceTests/GfxPerformance.cs b/src/Greenshot.PerformanceTests/GfxPerformance.cs index d732a7a07..8e66baa33 100644 --- a/src/Greenshot.PerformanceTests/GfxPerformance.cs +++ b/src/Greenshot.PerformanceTests/GfxPerformance.cs @@ -11,7 +11,7 @@ namespace Greenshot.PerformanceTests /// /// This defines the benchmarks which can be done /// - [MinColumn, MaxColumn, MemoryDiagnoser] + [MinColumn, MaxColumn, MemoryDiagnoser, CoreJob, ClrJob] public class GfxPerformance { private UnmanagedBitmap _unmanagedTestBitmap; diff --git a/src/Greenshot.PerformanceTests/GfxPerformanceShort.cs b/src/Greenshot.PerformanceTests/GfxPerformanceShort.cs deleted file mode 100644 index e84dd7eb4..000000000 --- a/src/Greenshot.PerformanceTests/GfxPerformanceShort.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Drawing; -using System.Drawing.Imaging; -using BenchmarkDotNet.Attributes; -using Greenshot.Gfx; - -namespace Greenshot.PerformanceTests -{ - /// - /// This defines the benchmarks which can be done - /// - [MinColumn, MaxColumn, MemoryDiagnoser] - public class GfxPerformanceShort - { - [Benchmark] - [Arguments(PixelFormat.Format24bppRgb)] - [Arguments(PixelFormat.Format32bppRgb)] - [Arguments(PixelFormat.Format32bppArgb)] - public void Blur(PixelFormat pixelFormat) - { - using (var bitmap = BitmapFactory.CreateEmpty(400, 400, pixelFormat, Color.White)) - { - using (var graphics = Graphics.FromImage(bitmap.NativeBitmap)) - using (var pen = new SolidBrush(Color.Blue)) - { - graphics.FillRectangle(pen, new Rectangle(30, 30, 340, 340)); - } - bitmap.ApplyBoxBlur(10); - } - } - } -} diff --git a/src/Greenshot.PerformanceTests/Greenshot.PerformanceTests.csproj b/src/Greenshot.PerformanceTests/Greenshot.PerformanceTests.csproj index 27934dcf2..893b072bc 100644 --- a/src/Greenshot.PerformanceTests/Greenshot.PerformanceTests.csproj +++ b/src/Greenshot.PerformanceTests/Greenshot.PerformanceTests.csproj @@ -64,9 +64,7 @@ - - 2.6.3 runtime; build; native; contentfiles; analyzers @@ -75,7 +73,6 @@ - diff --git a/src/Greenshot.PerformanceTests/Program.cs b/src/Greenshot.PerformanceTests/Program.cs index cc17d23d7..81a236aff 100644 --- a/src/Greenshot.PerformanceTests/Program.cs +++ b/src/Greenshot.PerformanceTests/Program.cs @@ -30,7 +30,8 @@ namespace Greenshot.PerformanceTests // ReSharper disable once UnusedParameter.Local private static void Main(string[] args) { - BenchmarkRunner.Run(); + //BenchmarkRunner.Run(); + BenchmarkRunner.Run(); Console.ReadLine(); } } diff --git a/src/Greenshot.Tests/CaptureTests.cs b/src/Greenshot.Tests/CaptureTests.cs index 2f613dcb7..403fc2c30 100644 --- a/src/Greenshot.Tests/CaptureTests.cs +++ b/src/Greenshot.Tests/CaptureTests.cs @@ -17,6 +17,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -128,5 +129,34 @@ namespace Greenshot.Tests await outputStream.CopyToAsync(fileStream); } } + + + /// + /// Test if capturing works + /// + [Fact] + public void Test_BitmapCapture() + { + using (var screenBitmapCapture = new BitmapScreenCapture()) + { + screenBitmapCapture.CaptureFrame(); + + Assert.NotNull(screenBitmapCapture.CurrentFrameAsBitmap()); + + var testFile1 = Path.Combine(Path.GetTempPath(), @"test-bitmap.png"); + screenBitmapCapture.CurrentFrameAsBitmap().NativeBitmap.Save(testFile1, ImageFormat.Png); + + var testFile2 = Path.Combine(Path.GetTempPath(), @"test-bitmapsource.png"); + using (var fileStream = new FileStream(testFile2, FileMode.Create)) + { + var encoder = new PngBitmapEncoder + { + Interlace = PngInterlaceOption.Off + }; + encoder.Frames.Add(BitmapFrame.Create(screenBitmapCapture.CurrentFrameAsBitmap().NativeBitmapSource)); + encoder.Save(fileStream); + } + } + } } }