1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-01 02:03:32 +00:00

Performance improvements

This commit is contained in:
SpoinkyNL 2017-01-18 19:44:39 +01:00
parent 6f3d00c8d4
commit 201e65b3b5
13 changed files with 175 additions and 94 deletions

View File

@ -503,7 +503,7 @@
<Compile Include="Profiles\Layers\Types\ConicalBrush\ConicalBrushType.cs" /> <Compile Include="Profiles\Layers\Types\ConicalBrush\ConicalBrushType.cs" />
<Compile Include="Profiles\Layers\Types\ConicalBrush\Drawing\ConicalGradientDrawer.cs" /> <Compile Include="Profiles\Layers\Types\ConicalBrush\Drawing\ConicalGradientDrawer.cs" />
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\AudioCapture.cs" /> <Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\AudioCapture.cs" />
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\AudioCaptureManager.cs" /> <Compile Include="Managers\AudioCaptureManager.cs" />
<Compile Include="Events\AudioDeviceChangedEventArgs.cs" /> <Compile Include="Events\AudioDeviceChangedEventArgs.cs" />
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\BaseSpectrumProvider.cs" /> <Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\BaseSpectrumProvider.cs" />
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\ISpectrumProvider.cs" /> <Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\ISpectrumProvider.cs" />

View File

@ -27,17 +27,11 @@ namespace Artemis.DeviceProviders
public abstract void Enable(); public abstract void Enable();
public abstract void DrawBitmap(Bitmap bitmap); public abstract void DrawBitmap(Bitmap bitmap);
/// <summary>
/// Returns a bitmap matching the keyboard's dimensions
/// </summary>
/// <returns></returns>
public Bitmap KeyboardBitmap() => new Bitmap(Width, Height);
/// <summary> /// <summary>
/// Returns a bitmap matching the keyboard's dimensions using the provided scale /// Returns a bitmap matching the keyboard's dimensions using the provided scale
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public Bitmap KeyboardBitmap(int scale) => new Bitmap(Width*scale, Height*scale); public Bitmap KeyboardBitmap(int scale = 4) => new Bitmap(Width*scale, Height*scale);
public Rect KeyboardRectangle(int scale = 4) => new Rect(new Size(Width*scale, Height*scale)); public Rect KeyboardRectangle(int scale = 4) => new Rect(new Size(Width*scale, Height*scale));

View File

@ -1,4 +1,5 @@
using Artemis.DeviceProviders; using Artemis.DeviceProviders;
using Artemis.Managers;
using Artemis.Models; using Artemis.Models;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Interfaces;
@ -104,9 +105,6 @@ namespace Artemis.InjectionModules
.InheritedFrom<ILayerType>() .InheritedFrom<ILayerType>()
.BindToSelf()); .BindToSelf());
// Type helpers
Bind<AudioCaptureManager>().ToSelf().InSingletonScope();
#endregion #endregion
#region Lua #region Lua

View File

@ -13,6 +13,7 @@ namespace Artemis.InjectionModules
Bind<ModuleManager>().ToSelf().InSingletonScope(); Bind<ModuleManager>().ToSelf().InSingletonScope();
Bind<PreviewManager>().ToSelf().InSingletonScope(); Bind<PreviewManager>().ToSelf().InSingletonScope();
Bind<LuaManager>().ToSelf().InSingletonScope(); Bind<LuaManager>().ToSelf().InSingletonScope();
Bind<AudioCaptureManager>().ToSelf().InSingletonScope();
} }
} }
} }

View File

@ -3,10 +3,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Timers; using System.Timers;
using Artemis.Events; using Artemis.Events;
using Artemis.Profiles.Layers.Types.Audio.AudioCapturing;
using CSCore.CoreAudioAPI; using CSCore.CoreAudioAPI;
using Ninject.Extensions.Logging; using Ninject.Extensions.Logging;
namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing namespace Artemis.Managers
{ {
public class AudioCaptureManager public class AudioCaptureManager
{ {

View File

@ -52,8 +52,8 @@ namespace Artemis.Managers
//TODO DarthAffe 14.01.2017: A stop-condition and a real cleanup instead of just aborting might be better //TODO DarthAffe 14.01.2017: A stop-condition and a real cleanup instead of just aborting might be better
while (true) while (true)
{ {
try // try
{ // {
long preUpdateTicks = DateTime.Now.Ticks; long preUpdateTicks = DateTime.Now.Ticks;
Render(); Render();
@ -61,11 +61,11 @@ namespace Artemis.Managers
int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f)); int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f));
if (sleep > 0) if (sleep > 0)
Thread.Sleep(sleep); Thread.Sleep(sleep);
} // }
catch (Exception e) // catch (Exception e)
{ // {
_logger.Warn(e, "Exception in render loop"); // _logger.Warn(e, "Exception in render loop");
} // }
} }
// ReSharper disable once FunctionNeverReturns // ReSharper disable once FunctionNeverReturns
} }
@ -207,19 +207,19 @@ namespace Artemis.Managers
if (keyboard == null) if (keyboard == null)
return; return;
KeyboardBitmap = keyboard.KeyboardBitmap(4); KeyboardBitmap = keyboard.KeyboardBitmap();
KeyboardBitmap.SetResolution(96, 96); KeyboardBitmap.SetResolution(96, 96);
MouseBitmap = new Bitmap(40, 40); MouseBitmap = new Bitmap(10, 10);
MouseBitmap.SetResolution(96, 96); MouseBitmap.SetResolution(96, 96);
HeadsetBitmap = new Bitmap(40, 40); HeadsetBitmap = new Bitmap(10, 10);
HeadsetBitmap.SetResolution(96, 96); HeadsetBitmap.SetResolution(96, 96);
GenericBitmap = new Bitmap(40, 40); GenericBitmap = new Bitmap(10, 10);
GenericBitmap.SetResolution(96, 96); GenericBitmap.SetResolution(96, 96);
MousematBitmap = new Bitmap(40, 40); MousematBitmap = new Bitmap(10, 10);
MousematBitmap.SetResolution(96, 96); MousematBitmap.SetResolution(96, 96);
using (var g = Graphics.FromImage(KeyboardBitmap)) using (var g = Graphics.FromImage(KeyboardBitmap))

View File

@ -163,7 +163,7 @@ namespace Artemis.Modules.Abstract
ProfileModel?.DrawLayers(g, layers, DrawType.Keyboard, DataModel, keyboardRect, preview); ProfileModel?.DrawLayers(g, layers, DrawType.Keyboard, DataModel, keyboardRect, preview);
} }
// Render mice layer-by-layer // Render mice layer-by-layer
var devRec = new Rect(0, 0, 40, 40); var devRec = new Rect(0, 0, 10, 10);
using (var g = Graphics.FromImage(frame.MouseBitmap)) using (var g = Graphics.FromImage(frame.MouseBitmap))
{ {
ProfileModel?.DrawLayers(g, layers, DrawType.Mouse, DataModel, devRec, preview); ProfileModel?.DrawLayers(g, layers, DrawType.Mouse, DataModel, devRec, preview);
@ -183,7 +183,7 @@ namespace Artemis.Modules.Abstract
{ {
ProfileModel?.DrawLayers(g, layers, DrawType.Mousemat, DataModel, devRec, preview); ProfileModel?.DrawLayers(g, layers, DrawType.Mousemat, DataModel, devRec, preview);
} }
// Trace debugging // Trace debugging
if (DateTime.Now.AddSeconds(-2) <= _lastTrace || Logger == null) if (DateTime.Now.AddSeconds(-2) <= _lastTrace || Logger == null)
return; return;

View File

@ -40,14 +40,8 @@ namespace Artemis.Modules.General.GeneralProfile
public class AudioDevice public class AudioDevice
{ {
public float OverallPeak { get; set; } public float OverallPeak { get; set; }
public float Channel1Peak { get; set; } public float LeftPeak { get; set; }
public float Channel2Peak { get; set; } public float RightPeak { get; set; }
public float Channel3Peak { get; set; }
public float Channel4Peak { get; set; }
public float Channel5Peak { get; set; }
public float Channel6Peak { get; set; }
public float Channel7Peak { get; set; }
public float Channel8Peak { get; set; }
} }
[MoonSharpUserData] [MoonSharpUserData]

View File

@ -10,7 +10,6 @@ using Artemis.DAL;
using Artemis.Events; using Artemis.Events;
using Artemis.Managers; using Artemis.Managers;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Artemis.Profiles.Layers.Types.Audio.AudioCapturing;
using Artemis.Utilities; using Artemis.Utilities;
using CSCore.CoreAudioAPI; using CSCore.CoreAudioAPI;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -20,7 +19,6 @@ namespace Artemis.Modules.General.GeneralProfile
{ {
public class GeneralProfileModel : ModuleModel public class GeneralProfileModel : ModuleModel
{ {
private readonly AudioCaptureManager _audioCaptureManager;
private DateTime _lastMusicUpdate; private DateTime _lastMusicUpdate;
private SpotifyLocalAPI _spotify; private SpotifyLocalAPI _spotify;
private bool _spotifySetupBusy; private bool _spotifySetupBusy;
@ -28,7 +26,6 @@ namespace Artemis.Modules.General.GeneralProfile
public GeneralProfileModel(DeviceManager deviceManager, LuaManager luaManager, public GeneralProfileModel(DeviceManager deviceManager, LuaManager luaManager,
AudioCaptureManager audioCaptureManager) : base(deviceManager, luaManager) AudioCaptureManager audioCaptureManager) : base(deviceManager, luaManager)
{ {
_audioCaptureManager = audioCaptureManager;
_lastMusicUpdate = DateTime.Now; _lastMusicUpdate = DateTime.Now;
Settings = SettingsProvider.Load<GeneralProfileSettings>(); Settings = SettingsProvider.Load<GeneralProfileSettings>();
@ -93,31 +90,26 @@ namespace Artemis.Modules.General.GeneralProfile
private void UpdateAudio(GeneralProfileDataModel dataModel) private void UpdateAudio(GeneralProfileDataModel dataModel)
{ {
// Update microphone, only bother with OverallPeak
var recording = AudioMeterInformation.FromDevice(_defaultRecording); if (_defaultRecording != null)
var playback = AudioMeterInformation.FromDevice(_defaultPlayback); {
var recording = AudioMeterInformation.FromDevice(_defaultRecording);
dataModel.Audio.Recording.OverallPeak = recording.PeakValue;
}
if (_defaultPlayback == null)
return;
// Update volume if a default device is found
dataModel.Audio.Volume = AudioEndpointVolume.FromDevice(_defaultPlayback).GetMasterVolumeLevelScalar(); dataModel.Audio.Volume = AudioEndpointVolume.FromDevice(_defaultPlayback).GetMasterVolumeLevelScalar();
dataModel.Audio.Recording.OverallPeak = recording.PeakValue;
for (var i = 0; i < recording.GetChannelsPeakValues(recording.MeteringChannelCount).Length; i++)
{
// Only support up to 8 channels until lists are supported natively
if (i > 7)
break;
var peakValue = recording.GetChannelsPeakValues(recording.MeteringChannelCount)[i];
typeof(AudioDevice).GetProperty($"Channel{i + 1}Peak").SetValue(dataModel.Audio.Recording, peakValue);
}
// Update speakers, only do overall, left and right for now
// TODO: When adding list support lets do all channels
var playback = AudioMeterInformation.FromDevice(_defaultPlayback);
var peakValues = playback.GetChannelsPeakValues();
dataModel.Audio.Playback.OverallPeak = playback.PeakValue; dataModel.Audio.Playback.OverallPeak = playback.PeakValue;
for (var i = 0; i < playback.GetChannelsPeakValues(playback.MeteringChannelCount).Length; i++) dataModel.Audio.Playback.LeftPeak = peakValues[0];
{ dataModel.Audio.Playback.LeftPeak = peakValues[1];
// Only support up to 8 channels until lists are supported natively
if (i > 7)
break;
var peakValue = playback.GetChannelsPeakValues(playback.MeteringChannelCount)[i];
typeof(AudioDevice).GetProperty($"Channel{i + 1}Peak").SetValue(dataModel.Audio.Playback, peakValue);
}
} }
#endregion #endregion

View File

@ -24,51 +24,87 @@ namespace Artemis.Profiles.Layers.Models
_yTweener = new Tweener<float>((float) layerModel.Y, (float) layerModel.Y, 0); _yTweener = new Tweener<float>((float) layerModel.Y, (float) layerModel.Y, 0);
_widthTweener = new Tweener<float>((float) layerModel.Width, (float) layerModel.Width, 0); _widthTweener = new Tweener<float>((float) layerModel.Width, (float) layerModel.Width, 0);
_heightTweener = new Tweener<float>((float) layerModel.Height, (float) layerModel.Height, 0); _heightTweener = new Tweener<float>((float) layerModel.Height, (float) layerModel.Height, 0);
_opacityTweener = new Tweener<float>((float) layerModel.Opacity, (float) layerModel.Opacity, 0); _opacityTweener = new Tweener<float>((float) layerModel.Opacity, (float) layerModel.Opacity, 0);
StoreCurrentValues(); _x = (float)_layerModel.X;
_y = (float)_layerModel.Y;
_width = (float)_layerModel.Width;
_height = (float)_layerModel.Height;
_opacity = (float)_layerModel.Opacity;
} }
public void Update() public void Update()
{ {
// Width UpdateWidth();
UpdateHeight();
UpdateOpacity();
}
private void UpdateWidth()
{
if (Math.Abs(_layerModel.Properties.WidthEaseTime) < 0.001)
return;
// Width
if (Math.Abs(_layerModel.Width - _width) > 0.001) if (Math.Abs(_layerModel.Width - _width) > 0.001)
{ {
var widthFunc = GetEaseFunction(_layerModel.Properties.WidthEase); var widthFunc = GetEaseFunction(_layerModel.Properties.WidthEase);
var widthSpeed = _layerModel.Properties.WidthEaseTime; var widthSpeed = _layerModel.Properties.WidthEaseTime;
_xTweener = new Tweener<float>(_xTweener.Value, (float) _layerModel.X, widthSpeed, widthFunc); _xTweener = new Tweener<float>(_xTweener.Value, (float)_layerModel.X, widthSpeed, widthFunc);
_widthTweener = new Tweener<float>(_widthTweener.Value, (float) _layerModel.Width, widthSpeed, widthFunc); _widthTweener = new Tweener<float>(_widthTweener.Value, (float)_layerModel.Width, widthSpeed, widthFunc);
} }
_xTweener.Update(40);
_widthTweener.Update(40);
_x = (float) _layerModel.X;
_width = (float)_layerModel.Width;
_layerModel.X = _xTweener.Value;
_layerModel.Width = _widthTweener.Value;
}
private void UpdateHeight()
{
if (Math.Abs(_layerModel.Properties.HeightEaseTime) < 0.001)
return;
// Height // Height
if (Math.Abs(_layerModel.Height - _height) > 0.001) if (Math.Abs(_layerModel.Height - _height) > 0.001)
{ {
var heightFunc = GetEaseFunction(_layerModel.Properties.HeightEase); var heightFunc = GetEaseFunction(_layerModel.Properties.HeightEase);
var heightSpeed = _layerModel.Properties.HeightEaseTime; var heightSpeed = _layerModel.Properties.HeightEaseTime;
_yTweener = new Tweener<float>(_y, (float) _layerModel.Y, heightSpeed, heightFunc); _yTweener = new Tweener<float>(_y, (float)_layerModel.Y, heightSpeed, heightFunc);
_heightTweener = new Tweener<float>(_height, (float) _layerModel.Height, heightSpeed, heightFunc); _heightTweener = new Tweener<float>(_height, (float)_layerModel.Height, heightSpeed, heightFunc);
} }
_yTweener.Update(40);
_heightTweener.Update(40);
_y = (float)_layerModel.Y;
_height = (float)_layerModel.Height;
_layerModel.Y = _yTweener.Value;
_layerModel.Height = _heightTweener.Value;
}
private void UpdateOpacity()
{
if (Math.Abs(_layerModel.Properties.OpacityEaseTime) < 0.001)
return;
// Opacity // Opacity
if (Math.Abs(_layerModel.Opacity - _opacity) > 0.001) if (Math.Abs(_layerModel.Opacity - _opacity) > 0.001)
{ {
_opacityTweener = new Tweener<float>(_opacity, (float) _layerModel.Opacity, _opacityTweener = new Tweener<float>(_opacity, (float)_layerModel.Opacity,
_layerModel.Properties.OpacityEaseTime, GetEaseFunction(_layerModel.Properties.OpacityEase)); _layerModel.Properties.OpacityEaseTime, GetEaseFunction(_layerModel.Properties.OpacityEase));
} }
_xTweener.Update(40);
_yTweener.Update(40);
_widthTweener.Update(40);
_heightTweener.Update(40);
_opacityTweener.Update(40); _opacityTweener.Update(40);
StoreCurrentValues(); _opacity = (float)_layerModel.Opacity;
_layerModel.X = _xTweener.Value;
_layerModel.Y = _yTweener.Value;
_layerModel.Width = _widthTweener.Value;
_layerModel.Height = _heightTweener.Value;
_layerModel.Opacity = _opacityTweener.Value; _layerModel.Opacity = _opacityTweener.Value;
} }

View File

@ -12,9 +12,11 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
{ {
public class AudioCapture public class AudioCapture
{ {
private const FftSize FftSize = CSCore.DSP.FftSize.Fft4096; private const FftSize FftSize = CSCore.DSP.FftSize.Fft1024;
private readonly Timer _volumeTimer; private readonly Timer _volumeTimer;
private readonly double[] _volumeValues; private readonly double[] _volumeValues;
private readonly Timer _disableTimer;
private bool _mayStop;
private SingleSpectrum _singleSpectrum; private SingleSpectrum _singleSpectrum;
private WasapiLoopbackCapture _soundIn; private WasapiLoopbackCapture _soundIn;
private GainSource _source; private GainSource _source;
@ -30,11 +32,12 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
_volumeValues = new double[5]; _volumeValues = new double[5];
_volumeIndex = 0; _volumeIndex = 0;
_disableTimer = new Timer(1000);
_disableTimer.Elapsed += CheckStop;
_volumeTimer = new Timer(200); _volumeTimer = new Timer(200);
_volumeTimer.Elapsed += VolumeTimerOnElapsed; _volumeTimer.Elapsed += VolumeTimerOnElapsed;
Start();
} }
public ILogger Logger { get; } public ILogger Logger { get; }
public MMDevice Device { get; } public MMDevice Device { get; }
public double DesiredAverage { get; set; } public double DesiredAverage { get; set; }
@ -45,6 +48,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
set { _volume.Volume = value; } set { _volume.Volume = value; }
} }
public bool Running { get; set; }
private void VolumeTimerOnElapsed(object sender, ElapsedEventArgs e) private void VolumeTimerOnElapsed(object sender, ElapsedEventArgs e)
{ {
if (Volume <= 0) if (Volume <= 0)
@ -100,12 +105,36 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
}; };
} }
/// <summary>
/// Keeps the audio capture active, when not called for longer than 1 sec the capture will
/// stop capturing until Pulse is called again
/// </summary>
public void Pulse()
{
_mayStop = false;
if (!Running)
Start();
}
private void CheckStop(object sender, ElapsedEventArgs e)
{
if (_mayStop)
{
Logger.Debug("Stopping idle audio capture for device: {0}", Device?.FriendlyName ?? "default");
Stop();
}
else
_mayStop = true;
}
private void Start() private void Start()
{ {
Logger.Debug("Starting audio capture for device: {0}", Device?.FriendlyName ?? "default"); Logger.Debug("Starting audio capture for device: {0}", Device?.FriendlyName ?? "default");
try try
{ {
Stop();
_soundIn = new WasapiLoopbackCapture(); _soundIn = new WasapiLoopbackCapture();
_soundIn.Initialize(); _soundIn.Initialize();
// Not sure if this null check is needed but doesnt hurt // Not sure if this null check is needed but doesnt hurt
@ -135,14 +164,39 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
}; };
_singleSpectrum = new SingleSpectrum(FftSize, _spectrumProvider); _singleSpectrum = new SingleSpectrum(FftSize, _spectrumProvider);
_mayStop = false;
_disableTimer.Start();
_volumeTimer.Start(); _volumeTimer.Start();
_soundIn.Start(); _soundIn.Start();
Running = true;
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warn(e, "Failed to start WASAPI audio capture"); Logger.Warn(e, "Failed to start WASAPI audio capture");
} }
} }
private void Stop()
{
Running = false;
if (_soundIn != null)
{
_soundIn.Stop();
_soundIn.Dispose();
_soundIn = null;
}
if (_source != null)
{
_source.Dispose();
_source = null;
}
_disableTimer.Stop();
_volumeTimer.Stop();
}
} }
} }

View File

@ -5,6 +5,7 @@ using System.Threading;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using Artemis.Events; using Artemis.Events;
using Artemis.Managers;
using Artemis.Modules.Abstract; using Artemis.Modules.Abstract;
using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Abstract;
using Artemis.Profiles.Layers.Animations; using Artemis.Profiles.Layers.Animations;
@ -122,6 +123,7 @@ namespace Artemis.Profiles.Layers.Types.Audio
public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false)
{ {
layerModel.ApplyProperties(true); layerModel.ApplyProperties(true);
_audioCapture.Pulse();
var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; var direction = ((AudioPropertiesModel) layerModel.Properties).Direction;

View File

@ -4,11 +4,15 @@ using System.IO;
using System.Windows; using System.Windows;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using PixelFormat = System.Drawing.Imaging.PixelFormat;
using Point = System.Drawing.Point;
namespace Artemis.Utilities namespace Artemis.Utilities
{ {
public class ImageUtilities public class ImageUtilities
{ {
private static RenderTargetBitmap _rBmp;
/// <summary> /// <summary>
/// Resize the image to the specified width and height. /// Resize the image to the specified width and height.
/// </summary> /// </summary>
@ -56,21 +60,26 @@ namespace Artemis.Utilities
public static Bitmap DrawingVisualToBitmap(DrawingVisual visual, Rect rect) public static Bitmap DrawingVisualToBitmap(DrawingVisual visual, Rect rect)
{ {
// TODO: Improve performance by dividing by 4 here var width = (int) rect.Width;
var bmp = new RenderTargetBitmap((int) rect.Width, (int) rect.Height, 96, 96, PixelFormats.Pbgra32); var height = (int) rect.Height;
bmp.Render(visual);
var encoder = new BmpBitmapEncoder(); // RenderTargetBitmap construction is expensive, only do it when needed
encoder.Frames.Add(BitmapFrame.Create(bmp)); if (_rBmp?.PixelHeight != height || _rBmp?.PixelWidth != width)
_rBmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
Bitmap bitmap; _rBmp.Render(visual);
using (var stream = new MemoryStream()) return GetBitmap(_rBmp);
{ }
encoder.Save(stream);
bitmap = new Bitmap(stream);
}
return bitmap; private static Bitmap GetBitmap(BitmapSource source)
{
var bmp = new Bitmap(source.PixelWidth, source.PixelHeight, PixelFormat.Format32bppPArgb);
bmp.SetResolution(96, 96);
var data = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly,
PixelFormat.Format32bppPArgb);
source.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride);
bmp.UnlockBits(data);
return bmp;
} }
/// <summary> /// <summary>