Make BitmapAnimator usage single-object

Don't unlock/lock the GlowBase bitmap repeatedly; store the bytes
separately for frame generating. Fixes any memory access exceptions
(unlock->unlock due to program lag).
Do more preprocessing of egg glow sprites for cleaner appearance (now
glow inside sprite)

extract glow method for easier reuse
This commit is contained in:
Kurt 2018-07-27 19:59:14 -07:00
parent 77bd91397a
commit 2e88da9d3c
4 changed files with 83 additions and 52 deletions

View file

@ -8,16 +8,16 @@ namespace PKHeX.WinForms.Controls
{
public sealed class BitmapAnimator : Timer
{
public BitmapAnimator(Bitmap baseImage, Bitmap extraLayer = null)
public BitmapAnimator(Image extraLayer = null)
{
GlowBase = baseImage;
ExtraLayer = extraLayer;
Elapsed += TimerElapsed;
}
private Bitmap GlowBase;
private Bitmap ExtraLayer;
private Bitmap[] GlowCache;
private Image GlowBase;
private byte[] GlowData;
private readonly Image ExtraLayer;
private Image[] GlowCache;
public Image OriginalBackground;
private PictureBox pb;
@ -27,29 +27,29 @@ namespace PKHeX.WinForms.Controls
public int GlowFps { get; set; } = 60;
public Color GlowToColor { get; set; } = Color.LightSkyBlue;
public Color GlowFromColor { get; set; } = Color.White;
private readonly object Lock = new object();
public new void Start() => throw new ArgumentException();
public new void Stop()
{
lock (Lock)
StopTimer();
}
private void StopTimer()
{
if (pb == null)
return;
Enabled = false;
// reset logic
GlowCounter = 0;
pb.BackgroundImage = OriginalBackground;
GlowBase = ExtraLayer = null;
OriginalBackground = null;
for (int i = 0; i < GlowCache.Length; i++)
GlowCache[i] = null;
}
public void Start(PictureBox pbox, Image original)
public void Start(PictureBox pbox, Image baseImage, byte[] glowData, Image original)
{
GlowBase = baseImage;
GlowData = glowData;
pb = pbox;
OriginalBackground = original;
GlowCache = new Bitmap[GlowFps];
GlowCache = new Image[GlowFps];
GlowInterval = 1000 / GlowFps;
Interval = GlowInterval;
Enabled = true;
@ -57,25 +57,14 @@ namespace PKHeX.WinForms.Controls
private void TimerElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
{
lock (Lock)
{
if (!Enabled)
return; // timer canceled, was waiting to proceed
GlowCounter = (GlowCounter + 1) % (GlowInterval * 2); // loop backwards
int frameIndex = GlowCounter >= GlowInterval ? (GlowInterval * 2) - GlowCounter : GlowCounter;
try
{
var frame = GetFrame(frameIndex);
pb.BackgroundImage = ImageUtil.LayerImage(OriginalBackground, frame, 0, 0, 1);
}
catch
{
StopTimer();
}
}
if (!Enabled)
return; // timer canceled, was waiting to proceed
GlowCounter = (GlowCounter + 1) % (GlowInterval * 2); // loop backwards
int frameIndex = GlowCounter >= GlowInterval ? (GlowInterval * 2) - GlowCounter : GlowCounter;
pb.BackgroundImage = GetFrame(frameIndex);
}
private Bitmap GetFrame(int frameIndex)
private Image GetFrame(int frameIndex)
{
var frame = GlowCache[frameIndex];
if (frame != null)
@ -83,9 +72,13 @@ namespace PKHeX.WinForms.Controls
var elapsedFraction = (double)frameIndex / GlowInterval;
var frameColor = GetFrameColor(elapsedFraction);
frame = ImageUtil.ChangeAllColorTo(GlowBase, frameColor);
var frameData = (byte[])GlowData.Clone();
ImageUtil.ChangeAllColorTo(frameData, frameColor);
frame = ImageUtil.GetBitmap(frameData, GlowBase.Width, GlowBase.Height);
if (ExtraLayer != null)
frame = ImageUtil.LayerImage(frame, ExtraLayer, 0, 0, 1);
frame = ImageUtil.LayerImage(OriginalBackground, frame, 0, 0, 1);
return GlowCache[frameIndex] = frame;
}

View file

@ -28,7 +28,7 @@ namespace PKHeX.WinForms.Controls
public bool GlowHover { get; set; } = true;
public Color GlowInitial { get; set; } = Color.White;
public Color GlowFinal { get; set; } = Color.LightSkyBlue;
public BitmapAnimator HoverWorker;
public readonly BitmapAnimator HoverWorker;
private SaveFile SAV => SE.SAV;
public SlotChangeInfo DragInfo;
@ -41,6 +41,7 @@ namespace PKHeX.WinForms.Controls
public SlotChangeManager(SAVEditor se)
{
HoverWorker = new BitmapAnimator(Resources.slotHover);
SE = se;
Reset();
}
@ -63,15 +64,6 @@ namespace PKHeX.WinForms.Controls
BeginHoverSlot(pb);
}
private Bitmap GetGlowSprite(PKM pk)
{
var baseSprite = SpriteUtil.GetSprite(pk.Species, pk.AltForm, pk.Gender, 0, pk.IsEgg, false, pk.Format);
var pixels = ImageUtil.GetPixelData((Bitmap)baseSprite);
ImageUtil.GlowEdges(pixels, new[] {GlowInitial.B, GlowInitial.G, GlowInitial.R}, baseSprite.Width);
return ImageUtil.GetBitmap(pixels, baseSprite.Width, baseSprite.Height);
}
private void BeginHoverSlot(PictureBox pb)
{
var view = WinFormsUtil.FindFirstControlOfType<ISlotViewer<PictureBox>>(pb);
@ -84,12 +76,14 @@ namespace PKHeX.WinForms.Controls
Bitmap hover;
if (GlowHover)
{
HoverWorker?.Stop();
HoverWorker.Stop();
var GlowBase = GetGlowSprite(pk);
var bgr = new[] { GlowInitial.B, GlowInitial.G, GlowInitial.R };
SpriteUtil.GetSpriteGlow(pk, bgr, out var glowdata, out var GlowBase);
hover = ImageUtil.LayerImage(GlowBase, Resources.slotHover, 0, 0);
HoverWorker = new BitmapAnimator(GlowBase, Resources.slotHover) { GlowFromColor = GlowInitial, GlowToColor = GlowFinal };
HoverWorker.Start(pb, OriginalBackground);
HoverWorker.GlowToColor = GlowFinal;
HoverWorker.GlowFromColor = GlowInitial;
HoverWorker.Start(pb, GlowBase, glowdata, OriginalBackground);
}
else
{
@ -108,8 +102,7 @@ namespace PKHeX.WinForms.Controls
{
if (HoveredSlot != null)
{
HoverWorker?.Stop();
HoverWorker = null;
HoverWorker.Stop();
HoveredSlot.BackgroundImage = OriginalBackground;
HoveredSlot = null;
}

View file

@ -58,7 +58,7 @@ namespace PKHeX.WinForms
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
SetAllColorTo(data, c);
ChangeAllColorTo(data, c);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
@ -106,13 +106,33 @@ namespace PKHeX.WinForms
return argbData;
}
public static void SetAllUsedPixelsOpaque(byte[] data)
{
for (int i = 0; i < data.Length; i += 4)
if (data[i + 3] != 0)
data[i + 3] = 0xFF;
}
public static void RemovePixels(byte[] pixels, byte[] original)
{
for (int i = 0; i < original.Length; i += 4)
{
if (original[i + 3] == 0)
continue;
pixels[i + 0] = 0;
pixels[i + 1] = 0;
pixels[i + 2] = 0;
pixels[i + 3] = 0;
}
}
private static void SetAllTransparencyTo(byte[] data, double trans)
{
for (int i = 0; i < data.Length; i += 4)
data[i + 3] = (byte)(data[i + 3] * trans);
}
private static void SetAllColorTo(byte[] data, Color c)
public static void ChangeAllColorTo(byte[] data, Color c)
{
byte R = c.R;
byte G = c.G;

View file

@ -107,6 +107,31 @@ namespace PKHeX.WinForms
return sprite;
}
public static void GetSpriteGlow(PKM pk, byte[] bgr, out byte[] pixels, out Image baseSprite, bool forceHollow = false)
{
bool egg = pk.IsEgg;
baseSprite = GetSprite(pk.Species, pk.AltForm, pk.Gender, 0, egg, false, pk.Format);
GetSpriteGlow(baseSprite, bgr, out pixels, forceHollow || egg);
}
public static void GetSpriteGlow(Image baseSprite, byte[] bgr, out byte[] pixels, bool forceHollow = false)
{
pixels = ImageUtil.GetPixelData((Bitmap) baseSprite);
if (!forceHollow)
{
ImageUtil.GlowEdges(pixels, bgr, baseSprite.Width);
return;
}
// If the image has any transparency, any derived background will bleed into it.
// Need to undo any transparency values if any present.
// Remove opaque pixels from original image, leaving only the glow effect pixels.
var original = (byte[]) pixels.Clone();
ImageUtil.SetAllUsedPixelsOpaque(pixels);
ImageUtil.GlowEdges(pixels, bgr, baseSprite.Width);
ImageUtil.RemovePixels(pixels, original);
}
// Extension Methods
public static Image WallpaperImage(this SaveFile SAV, int box) => GetWallpaper(SAV, box);
public static Image Sprite(this MysteryGift gift) => GetSprite(gift);