PKHeX/PKHeX.WinForms/Controls/SAV Editor/BitmapAnimator.cs
Kurt 88830e0d00
Update from .NET Framework 4.6 to .NET 7 (#3729)
Updates from net46->net7, dropping support for mono in favor of using the latest runtime (along with the performance/API improvements). Releases will be posted as 64bit only for now.

Refactors a good amount of internal API methods to be more performant and more customizable for future updates & fixes.

Adds functionality for Batch Editor commands to `>`, `<` and <=/>=

TID/SID properties renamed to TID16/SID16 for clarity; other properties exposed for Gen7 / display variants.

Main window has a new layout to account for DPI scaling (8 point grid)

Fixed: Tatsugiri and Paldean Tauros now output Showdown form names as Showdown expects
Changed: Gen9 species now interact based on the confirmed National Dex IDs (closes #3724)
Fixed: Pokedex set all no longer clears species with unavailable non-base forms (closes #3720)
Changed: Hyper Training suggestions now apply for level 50 in SV. (closes #3714)
Fixed: B2/W2 hatched egg met locations exclusive to specific versions are now explicitly checked (closes #3691)
Added: Properties for ribbon/mark count (closes #3659)
Fixed: Traded SV eggs are now checked correctly (closes #3692)
2023-01-21 20:02:33 -08:00

124 lines
3.7 KiB
C#

using System;
using System.Drawing;
using System.Timers;
using System.Windows.Forms;
using PKHeX.Drawing;
using Timer = System.Timers.Timer;
namespace PKHeX.WinForms.Controls;
public sealed class BitmapAnimator : IDisposable
{
public BitmapAnimator() => Timer.Elapsed += TimerElapsed;
private readonly Timer Timer = new();
private int imgWidth;
private int imgHeight;
private byte[]? GlowData;
private Image? ExtraLayer;
private Image?[]? GlowCache;
private Image? OriginalBackground;
private readonly object Lock = new();
private PictureBox? pb;
private int GlowInterval;
private int GlowCounter;
public int GlowFps { get; set; } = 60;
public Color GlowToColor { get; set; } = Color.LightSkyBlue;
public Color GlowFromColor { get; set; } = Color.White;
public bool Enabled { get => Timer.Enabled; set => Timer.Enabled = value; }
public void Stop()
{
if (pb == null || !Enabled)
return;
lock (Lock)
{
Enabled = false;
pb.BackgroundImage = OriginalBackground;
}
// reset logic
if (GlowCache == null)
throw new ArgumentNullException(nameof(GlowCache));
GlowCounter = 0;
for (int i = 0; i < GlowCache.Length; i++)
GlowCache[i] = null;
}
public void Start(PictureBox pbox, Image baseImage, byte[] glowData, Image? original, Image extra)
{
Enabled = false;
imgWidth = baseImage.Width;
imgHeight = baseImage.Height;
GlowData = glowData;
GlowCounter = 0;
GlowCache = new Image[GlowFps];
GlowInterval = 1000 / GlowFps;
Timer.Interval = GlowInterval;
lock (Lock)
{
pb = pbox;
ExtraLayer = extra;
OriginalBackground = original;
}
Enabled = true;
}
private void TimerElapsed(object? sender, ElapsedEventArgs? elapsedEventArgs)
{
if (!Enabled)
return; // timer canceled, was waiting to proceed
GlowCounter = (GlowCounter + 1) % (GlowInterval * 2); // loop backwards
int frameIndex = GlowCounter >= GlowInterval ? (GlowInterval * 2) - GlowCounter : GlowCounter;
lock (Lock)
{
if (!Enabled)
return;
if (pb == null)
return;
try { pb.BackgroundImage = GetFrame(frameIndex); } // drawing GDI can be silly sometimes #2072
catch (AccessViolationException ex) { System.Diagnostics.Debug.WriteLine(ex.Message); }
}
}
private Image GetFrame(int frameIndex)
{
var cache = GlowCache;
if (cache == null)
throw new NullReferenceException(nameof(GlowCache));
var frame = cache[frameIndex];
if (frame != null)
return frame;
var elapsedFraction = (double)frameIndex / GlowInterval;
var frameColor = GetFrameColor(elapsedFraction);
if (GlowData == null)
throw new NullReferenceException(nameof(GlowData));
var frameData = (byte[])GlowData.Clone();
ImageUtil.ChangeAllColorTo(frameData, frameColor);
frame = ImageUtil.GetBitmap(frameData, imgWidth, imgHeight);
if (ExtraLayer != null)
frame = ImageUtil.LayerImage(frame, ExtraLayer, 0, 0);
if (OriginalBackground != null)
frame = ImageUtil.LayerImage(OriginalBackground, frame, 0, 0);
return cache[frameIndex] = frame;
}
private Color GetFrameColor(double elapsedFraction) => ColorUtil.Blend(GlowToColor, GlowFromColor, elapsedFraction);
public void Dispose()
{
GlowCache = null;
Timer.Enabled = false;
Timer.Elapsed -= TimerElapsed;
Timer.Dispose();
}
}