mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Performance improvements
This commit is contained in:
parent
6f3d00c8d4
commit
201e65b3b5
@ -503,7 +503,7 @@
|
||||
<Compile Include="Profiles\Layers\Types\ConicalBrush\ConicalBrushType.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\AudioCaptureManager.cs" />
|
||||
<Compile Include="Managers\AudioCaptureManager.cs" />
|
||||
<Compile Include="Events\AudioDeviceChangedEventArgs.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\BaseSpectrumProvider.cs" />
|
||||
<Compile Include="Profiles\Layers\Types\Audio\AudioCapturing\ISpectrumProvider.cs" />
|
||||
|
||||
@ -27,17 +27,11 @@ namespace Artemis.DeviceProviders
|
||||
public abstract void Enable();
|
||||
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>
|
||||
/// Returns a bitmap matching the keyboard's dimensions using the provided scale
|
||||
/// </summary>
|
||||
/// <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));
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Artemis.DeviceProviders;
|
||||
using Artemis.Managers;
|
||||
using Artemis.Models;
|
||||
using Artemis.Modules.Abstract;
|
||||
using Artemis.Profiles.Layers.Interfaces;
|
||||
@ -104,9 +105,6 @@ namespace Artemis.InjectionModules
|
||||
.InheritedFrom<ILayerType>()
|
||||
.BindToSelf());
|
||||
|
||||
// Type helpers
|
||||
Bind<AudioCaptureManager>().ToSelf().InSingletonScope();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lua
|
||||
|
||||
@ -13,6 +13,7 @@ namespace Artemis.InjectionModules
|
||||
Bind<ModuleManager>().ToSelf().InSingletonScope();
|
||||
Bind<PreviewManager>().ToSelf().InSingletonScope();
|
||||
Bind<LuaManager>().ToSelf().InSingletonScope();
|
||||
Bind<AudioCaptureManager>().ToSelf().InSingletonScope();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,10 +3,11 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Timers;
|
||||
using Artemis.Events;
|
||||
using Artemis.Profiles.Layers.Types.Audio.AudioCapturing;
|
||||
using CSCore.CoreAudioAPI;
|
||||
using Ninject.Extensions.Logging;
|
||||
|
||||
namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
|
||||
namespace Artemis.Managers
|
||||
{
|
||||
public class AudioCaptureManager
|
||||
{
|
||||
@ -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
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// try
|
||||
// {
|
||||
long preUpdateTicks = DateTime.Now.Ticks;
|
||||
|
||||
Render();
|
||||
@ -61,11 +61,11 @@ namespace Artemis.Managers
|
||||
int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f));
|
||||
if (sleep > 0)
|
||||
Thread.Sleep(sleep);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "Exception in render loop");
|
||||
}
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// _logger.Warn(e, "Exception in render loop");
|
||||
// }
|
||||
}
|
||||
// ReSharper disable once FunctionNeverReturns
|
||||
}
|
||||
@ -207,19 +207,19 @@ namespace Artemis.Managers
|
||||
if (keyboard == null)
|
||||
return;
|
||||
|
||||
KeyboardBitmap = keyboard.KeyboardBitmap(4);
|
||||
KeyboardBitmap = keyboard.KeyboardBitmap();
|
||||
KeyboardBitmap.SetResolution(96, 96);
|
||||
|
||||
MouseBitmap = new Bitmap(40, 40);
|
||||
MouseBitmap = new Bitmap(10, 10);
|
||||
MouseBitmap.SetResolution(96, 96);
|
||||
|
||||
HeadsetBitmap = new Bitmap(40, 40);
|
||||
HeadsetBitmap = new Bitmap(10, 10);
|
||||
HeadsetBitmap.SetResolution(96, 96);
|
||||
|
||||
GenericBitmap = new Bitmap(40, 40);
|
||||
GenericBitmap = new Bitmap(10, 10);
|
||||
GenericBitmap.SetResolution(96, 96);
|
||||
|
||||
MousematBitmap = new Bitmap(40, 40);
|
||||
MousematBitmap = new Bitmap(10, 10);
|
||||
MousematBitmap.SetResolution(96, 96);
|
||||
|
||||
using (var g = Graphics.FromImage(KeyboardBitmap))
|
||||
|
||||
@ -163,7 +163,7 @@ namespace Artemis.Modules.Abstract
|
||||
ProfileModel?.DrawLayers(g, layers, DrawType.Keyboard, DataModel, keyboardRect, preview);
|
||||
}
|
||||
// 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))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
// Trace debugging
|
||||
if (DateTime.Now.AddSeconds(-2) <= _lastTrace || Logger == null)
|
||||
return;
|
||||
|
||||
@ -40,14 +40,8 @@ namespace Artemis.Modules.General.GeneralProfile
|
||||
public class AudioDevice
|
||||
{
|
||||
public float OverallPeak { get; set; }
|
||||
public float Channel1Peak { get; set; }
|
||||
public float Channel2Peak { 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; }
|
||||
public float LeftPeak { get; set; }
|
||||
public float RightPeak { get; set; }
|
||||
}
|
||||
|
||||
[MoonSharpUserData]
|
||||
|
||||
@ -10,7 +10,6 @@ using Artemis.DAL;
|
||||
using Artemis.Events;
|
||||
using Artemis.Managers;
|
||||
using Artemis.Modules.Abstract;
|
||||
using Artemis.Profiles.Layers.Types.Audio.AudioCapturing;
|
||||
using Artemis.Utilities;
|
||||
using CSCore.CoreAudioAPI;
|
||||
using Newtonsoft.Json;
|
||||
@ -20,7 +19,6 @@ namespace Artemis.Modules.General.GeneralProfile
|
||||
{
|
||||
public class GeneralProfileModel : ModuleModel
|
||||
{
|
||||
private readonly AudioCaptureManager _audioCaptureManager;
|
||||
private DateTime _lastMusicUpdate;
|
||||
private SpotifyLocalAPI _spotify;
|
||||
private bool _spotifySetupBusy;
|
||||
@ -28,7 +26,6 @@ namespace Artemis.Modules.General.GeneralProfile
|
||||
public GeneralProfileModel(DeviceManager deviceManager, LuaManager luaManager,
|
||||
AudioCaptureManager audioCaptureManager) : base(deviceManager, luaManager)
|
||||
{
|
||||
_audioCaptureManager = audioCaptureManager;
|
||||
_lastMusicUpdate = DateTime.Now;
|
||||
|
||||
Settings = SettingsProvider.Load<GeneralProfileSettings>();
|
||||
@ -93,31 +90,26 @@ namespace Artemis.Modules.General.GeneralProfile
|
||||
|
||||
private void UpdateAudio(GeneralProfileDataModel dataModel)
|
||||
{
|
||||
|
||||
var recording = AudioMeterInformation.FromDevice(_defaultRecording);
|
||||
var playback = AudioMeterInformation.FromDevice(_defaultPlayback);
|
||||
// Update microphone, only bother with OverallPeak
|
||||
if (_defaultRecording != null)
|
||||
{
|
||||
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.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;
|
||||
for (var i = 0; i < playback.GetChannelsPeakValues(playback.MeteringChannelCount).Length; i++)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
dataModel.Audio.Playback.LeftPeak = peakValues[0];
|
||||
dataModel.Audio.Playback.LeftPeak = peakValues[1];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -24,51 +24,87 @@ namespace Artemis.Profiles.Layers.Models
|
||||
_yTweener = new Tweener<float>((float) layerModel.Y, (float) layerModel.Y, 0);
|
||||
_widthTweener = new Tweener<float>((float) layerModel.Width, (float) layerModel.Width, 0);
|
||||
_heightTweener = new Tweener<float>((float) layerModel.Height, (float) layerModel.Height, 0);
|
||||
_opacityTweener = new Tweener<float>((float) layerModel.Opacity, (float) layerModel.Opacity, 0);
|
||||
|
||||
StoreCurrentValues();
|
||||
_opacityTweener = new Tweener<float>((float) layerModel.Opacity, (float) layerModel.Opacity, 0);
|
||||
|
||||
_x = (float)_layerModel.X;
|
||||
_y = (float)_layerModel.Y;
|
||||
_width = (float)_layerModel.Width;
|
||||
_height = (float)_layerModel.Height;
|
||||
_opacity = (float)_layerModel.Opacity;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var widthFunc = GetEaseFunction(_layerModel.Properties.WidthEase);
|
||||
var widthSpeed = _layerModel.Properties.WidthEaseTime;
|
||||
|
||||
_xTweener = new Tweener<float>(_xTweener.Value, (float) _layerModel.X, widthSpeed, widthFunc);
|
||||
_widthTweener = new Tweener<float>(_widthTweener.Value, (float) _layerModel.Width, widthSpeed, widthFunc);
|
||||
}
|
||||
_xTweener = new Tweener<float>(_xTweener.Value, (float)_layerModel.X, 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
|
||||
if (Math.Abs(_layerModel.Height - _height) > 0.001)
|
||||
{
|
||||
var heightFunc = GetEaseFunction(_layerModel.Properties.HeightEase);
|
||||
var heightSpeed = _layerModel.Properties.HeightEaseTime;
|
||||
_yTweener = new Tweener<float>(_y, (float) _layerModel.Y, heightSpeed, heightFunc);
|
||||
_heightTweener = new Tweener<float>(_height, (float) _layerModel.Height, heightSpeed, heightFunc);
|
||||
}
|
||||
_yTweener = new Tweener<float>(_y, (float)_layerModel.Y, 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
|
||||
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));
|
||||
}
|
||||
|
||||
_xTweener.Update(40);
|
||||
_yTweener.Update(40);
|
||||
_widthTweener.Update(40);
|
||||
_heightTweener.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;
|
||||
}
|
||||
|
||||
|
||||
@ -12,9 +12,11 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
|
||||
{
|
||||
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 double[] _volumeValues;
|
||||
private readonly Timer _disableTimer;
|
||||
private bool _mayStop;
|
||||
private SingleSpectrum _singleSpectrum;
|
||||
private WasapiLoopbackCapture _soundIn;
|
||||
private GainSource _source;
|
||||
@ -30,11 +32,12 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
|
||||
|
||||
_volumeValues = new double[5];
|
||||
_volumeIndex = 0;
|
||||
_disableTimer = new Timer(1000);
|
||||
_disableTimer.Elapsed += CheckStop;
|
||||
_volumeTimer = new Timer(200);
|
||||
_volumeTimer.Elapsed += VolumeTimerOnElapsed;
|
||||
Start();
|
||||
}
|
||||
|
||||
|
||||
public ILogger Logger { get; }
|
||||
public MMDevice Device { get; }
|
||||
public double DesiredAverage { get; set; }
|
||||
@ -45,6 +48,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing
|
||||
set { _volume.Volume = value; }
|
||||
}
|
||||
|
||||
public bool Running { get; set; }
|
||||
|
||||
private void VolumeTimerOnElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
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()
|
||||
{
|
||||
Logger.Debug("Starting audio capture for device: {0}", Device?.FriendlyName ?? "default");
|
||||
|
||||
try
|
||||
{
|
||||
Stop();
|
||||
|
||||
_soundIn = new WasapiLoopbackCapture();
|
||||
_soundIn.Initialize();
|
||||
// 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);
|
||||
_mayStop = false;
|
||||
|
||||
_disableTimer.Start();
|
||||
_volumeTimer.Start();
|
||||
_soundIn.Start();
|
||||
|
||||
Running = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Events;
|
||||
using Artemis.Managers;
|
||||
using Artemis.Modules.Abstract;
|
||||
using Artemis.Profiles.Layers.Abstract;
|
||||
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)
|
||||
{
|
||||
layerModel.ApplyProperties(true);
|
||||
_audioCapture.Pulse();
|
||||
|
||||
var direction = ((AudioPropertiesModel) layerModel.Properties).Direction;
|
||||
|
||||
|
||||
@ -4,11 +4,15 @@ using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using PixelFormat = System.Drawing.Imaging.PixelFormat;
|
||||
using Point = System.Drawing.Point;
|
||||
|
||||
namespace Artemis.Utilities
|
||||
{
|
||||
public class ImageUtilities
|
||||
{
|
||||
private static RenderTargetBitmap _rBmp;
|
||||
|
||||
/// <summary>
|
||||
/// Resize the image to the specified width and height.
|
||||
/// </summary>
|
||||
@ -56,21 +60,26 @@ namespace Artemis.Utilities
|
||||
|
||||
public static Bitmap DrawingVisualToBitmap(DrawingVisual visual, Rect rect)
|
||||
{
|
||||
// TODO: Improve performance by dividing by 4 here
|
||||
var bmp = new RenderTargetBitmap((int) rect.Width, (int) rect.Height, 96, 96, PixelFormats.Pbgra32);
|
||||
bmp.Render(visual);
|
||||
var width = (int) rect.Width;
|
||||
var height = (int) rect.Height;
|
||||
|
||||
var encoder = new BmpBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(bmp));
|
||||
// RenderTargetBitmap construction is expensive, only do it when needed
|
||||
if (_rBmp?.PixelHeight != height || _rBmp?.PixelWidth != width)
|
||||
_rBmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
|
||||
|
||||
Bitmap bitmap;
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
encoder.Save(stream);
|
||||
bitmap = new Bitmap(stream);
|
||||
}
|
||||
_rBmp.Render(visual);
|
||||
return GetBitmap(_rBmp);
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user