From 9861128b63f6cdb7c718ef340bd124b78cc69169 Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 22 Apr 2019 22:24:29 -0700 Subject: [PATCH] Minor img processing update Extract magic pixel shifting numbers for layering imgs -- SWSH will likely use the large boxsprites, as hinted by LGPE's upsizing. Remove new[] color array creation, just pass byte values break up glowedges into steps --- .../Controls/SAV Editor/SlotChangeManager.cs | 3 +- PKHeX.WinForms/Util/ImageUtil.cs | 31 ++++++--- PKHeX.WinForms/Util/SpriteBuilder.cs | 38 ++++++----- PKHeX.WinForms/Util/SpriteUtil.cs | 65 ++++++++++++------- 4 files changed, 84 insertions(+), 53 deletions(-) diff --git a/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs b/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs index 2e59ed455..a805021da 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs @@ -78,8 +78,7 @@ namespace PKHeX.WinForms.Controls { HoverWorker.Stop(); - var bgr = new[] { Draw.GlowInitial.B, Draw.GlowInitial.G, Draw.GlowInitial.R }; - SpriteUtil.GetSpriteGlow(pk, bgr, out var glowdata, out var GlowBase); + SpriteUtil.GetSpriteGlow(pk, Draw.GlowInitial.B, Draw.GlowInitial.G, Draw.GlowInitial.R, out var glowdata, out var GlowBase); hover = ImageUtil.LayerImage(GlowBase, Resources.slotHover, 0, 0); HoverWorker.GlowToColor = Draw.GlowFinal; HoverWorker.GlowFromColor = Draw.GlowInitial; diff --git a/PKHeX.WinForms/Util/ImageUtil.cs b/PKHeX.WinForms/Util/ImageUtil.cs index 073d705bd..b658c1613 100644 --- a/PKHeX.WinForms/Util/ImageUtil.cs +++ b/PKHeX.WinForms/Util/ImageUtil.cs @@ -164,13 +164,19 @@ namespace PKHeX.WinForms } } - public static void GlowEdges(byte[] data, byte[] colors, int width, int reach = 3, double amount = 0.0777) + public static void GlowEdges(byte[] data, byte blue, byte green, byte red, int width, int reach = 3, double amount = 0.0777) + { + PollutePixels(data, width, reach, amount); + CleanPollutedPixels(data, blue, green, red); + } + + private static void PollutePixels(byte[] data, int width, int reach, double amount) { - // dual pass (pollute, de-transparent) int stride = width * 4; int height = data.Length / stride; for (int i = 0; i < data.Length; i += 4) { + // only pollute outwards if the current pixel isn't transparent if (data[i + 3] == 0) continue; @@ -189,23 +195,32 @@ namespace PKHeX.WinForms { for (int j = top; j <= bottom; j++) { + // update one of the color bits + // it is expected that a transparent pixel RGBA value is 0. var c = 4 * (i + (j * width)); data[c + 0] += (byte)(amount * (0xFF - data[c + 0])); } } } + } + + private static void CleanPollutedPixels(byte[] data, byte blue, byte green, byte red) + { for (int i = 0; i < data.Length; i += 4) { + // only clean if the current pixel isn't transparent if (data[i + 3] != 0) continue; - var flair = data[i + 0]; - if (flair == 0) + + // grab the transparency from the donor byte + var transparency = data[i + 0]; + if (transparency == 0) continue; - data[i + 3] = flair; - data[i + 0] = colors[0]; - data[i + 1] = colors[1]; - data[i + 2] = colors[2]; + data[i + 0] = blue; + data[i + 1] = green; + data[i + 2] = red; + data[i + 3] = transparency; } } diff --git a/PKHeX.WinForms/Util/SpriteBuilder.cs b/PKHeX.WinForms/Util/SpriteBuilder.cs index fd060893a..3949adae8 100644 --- a/PKHeX.WinForms/Util/SpriteBuilder.cs +++ b/PKHeX.WinForms/Util/SpriteBuilder.cs @@ -8,6 +8,15 @@ namespace PKHeX.WinForms { public static bool ShowEggSpriteAsItem { get; set; } = true; + private const int ItemShiftX = 22; + private const int ItemShiftY = 15; + private const int ItemMaxSize = 15; + private const int EggItemShiftX = 9; + private const int EggItemShiftY = 2; + private const double UnknownFormTransparency = 0.5; + private const double ShinyTransparency = 0.7; + private const double EggUnderLayerTransparency = 0.33; + public void Initialize(SaveFile sav) { if (sav.Generation != 3) @@ -66,8 +75,7 @@ namespace PKHeX.WinForms private static Image GetBaseImageTotem(int species, int form, int gender, bool shiny, int generation) { var baseform = FormConverter.GetTotemBaseForm(species, form); - var file = PKX.GetResourceStringSprite(species, baseform, gender, generation, shiny); - var baseImage = (Image)Resources.ResourceManager.GetObject(file); + var baseImage = GetBaseImageDefault(species, baseform, gender, shiny, generation); return ImageUtil.ToGrayscale(baseImage); } @@ -79,20 +87,18 @@ namespace PKHeX.WinForms private static Image GetBaseImageFallback(int species, int form, int gender, bool shiny, int generation) { - Image baseImage; if (shiny) // try again without shiny { - var file = PKX.GetResourceStringSprite(species, form, gender, generation); - baseImage = (Image)Resources.ResourceManager.GetObject(file); - if (baseImage != null) - return baseImage; + var img = GetBaseImageDefault(species, form, gender, false, generation); + if (img != null) + return img; } // try again without form - baseImage = (Image)Resources.ResourceManager.GetObject($"_{species}"); + var baseImage = (Image)Resources.ResourceManager.GetObject($"_{species}"); if (baseImage == null) // failed again return Resources.unknown; - return ImageUtil.LayerImage(baseImage, Resources.unknown, 0, 0, .5); + return ImageUtil.LayerImage(baseImage, Resources.unknown, 0, 0, UnknownFormTransparency); } private static Image LayerOverImageItem(Image baseImage, int item, int generation) @@ -101,11 +107,11 @@ namespace PKHeX.WinForms if (generation >= 2 && generation <= 4 && 328 <= item && item <= 419) // gen2/3/4 TM itemimg = Resources.item_tm; - // Redraw - int x = 22 + ((15 - itemimg.Width) / 2); + // Redraw item in bottom right corner; since images are cropped, try to not have them at the edge + int x = ItemShiftX + ((ItemMaxSize - itemimg.Width) / 2); if (x + itemimg.Width > baseImage.Width) x = baseImage.Width - itemimg.Width; - int y = 15 + (15 - itemimg.Height); + int y = ItemShiftY + (ItemMaxSize - itemimg.Height); return ImageUtil.LayerImage(baseImage, itemimg, x, y); } @@ -113,7 +119,7 @@ namespace PKHeX.WinForms { // Add shiny star to top left of image. var rare = isBoxBGRed ? Resources.rare_icon_alt : Resources.rare_icon; - return ImageUtil.LayerImage(baseImage, rare, 0, 0, 0.7); + return ImageUtil.LayerImage(baseImage, rare, 0, 0, ShinyTransparency); } private static Image LayerOverImageEgg(Image baseImage, int species, bool hasItem) @@ -125,10 +131,6 @@ namespace PKHeX.WinForms private static Image GetEggSprite(int species) => species == 490 ? Resources._490_e : Resources.egg; - private const double EggUnderLayerTransparency = 0.33; - private const int EggOverLayerAsItemShiftX = 9; - private const int EggOverLayerAsItemShiftY = 2; - private static Image LayerOverImageEggTransparentSpecies(Image baseImage, int species) { // Partially transparent species. @@ -141,7 +143,7 @@ namespace PKHeX.WinForms private static Image LayerOverImageEggAsItem(Image baseImage, int species) { var egg = GetEggSprite(species); - return ImageUtil.LayerImage(baseImage, egg, EggOverLayerAsItemShiftX, EggOverLayerAsItemShiftY); // similar to held item, since they can't have any + return ImageUtil.LayerImage(baseImage, egg, EggItemShiftX, EggItemShiftY); // similar to held item, since they can't have any } } } diff --git a/PKHeX.WinForms/Util/SpriteUtil.cs b/PKHeX.WinForms/Util/SpriteUtil.cs index 730ec98fe..b83f1959b 100644 --- a/PKHeX.WinForms/Util/SpriteUtil.cs +++ b/PKHeX.WinForms/Util/SpriteUtil.cs @@ -10,8 +10,8 @@ namespace PKHeX.WinForms public static Image GetBallSprite(int ball) { - string str = PKX.GetResourceStringBall(ball); - return (Image)Resources.ResourceManager.GetObject(str) ?? Resources._ball4; // Poké Ball (default) + string resource = PKX.GetResourceStringBall(ball); + return (Image)Resources.ResourceManager.GetObject(resource) ?? Resources._ball4; // Poké Ball (default) } public static Image GetSprite(int species, int form, int gender, int item, bool isegg, bool shiny, int generation = -1, bool isBoxBGRed = false) @@ -21,8 +21,8 @@ namespace PKHeX.WinForms public static Image GetRibbonSprite(string name) { - var sprite = name.Replace("CountG3", "G3").ToLower(); - return Resources.ResourceManager.GetObject(sprite) as Image; + var resource = name.Replace("CountG3", "G3").ToLower(); + return (Bitmap)Resources.ResourceManager.GetObject(resource); } public static Image GetRibbonSprite(string name, int max, int value) @@ -57,7 +57,7 @@ namespace PKHeX.WinForms { if (generation <= 2) type = (int)((MoveType)type).GetMoveTypeGeneration(generation); - return Resources.ResourceManager.GetObject($"type_icon_{type:00}") as Image; + return (Bitmap)Resources.ResourceManager.GetObject($"type_icon_{type:00}"); } private static Image GetSprite(MysteryGift gift) @@ -82,7 +82,7 @@ namespace PKHeX.WinForms int item = gift.ItemID; if (Legal.ZCrystalDictionary.TryGetValue(item, out int value)) item = value; - return (Image)(Resources.ResourceManager.GetObject("item_" + item) ?? Resources.Bag_Key); + return (Image)(Resources.ResourceManager.GetObject($"item_{item}") ?? Resources.Bag_Key); } return Resources.unknown; } @@ -92,9 +92,10 @@ namespace PKHeX.WinForms var img = GetSprite(pk.Species, pk.AltForm, pk.Gender, pk.SpriteItem, pk.IsEgg, pk.IsShiny, pk.Format, isBoxBGRed); if (pk is IShadowPKM s && s.Purification > 0) { - if (pk.Species == 249) // Lugia - img = Spriter.GetSprite(Resources._249x, 249, pk.HeldItem, pk.IsEgg, pk.IsShiny, pk.Format, isBoxBGRed); - GetSpriteGlow(pk, new byte[] { 75, 0, 130 }, out var pixels, out var baseSprite, true); + const int Lugia = 249; + if (pk.Species == Lugia) // show XD shadow sprite + img = Spriter.GetSprite(Resources._249x, Lugia, pk.HeldItem, pk.IsEgg, pk.IsShiny, pk.Format, isBoxBGRed); + GetSpriteGlow(pk, 75, 0, 130, out var pixels, out var baseSprite, true); var glowImg = ImageUtil.GetBitmap(pixels, baseSprite.Width, baseSprite.Height, baseSprite.PixelFormat); img = ImageUtil.LayerImage(glowImg, img, 0, 0); } @@ -120,28 +121,29 @@ namespace PKHeX.WinForms if (!pk.Valid) return null; - bool inBox = slot >= 0 && slot < 30; - var sprite = pk.Species == 0 ? null : pk.Sprite(isBoxBGRed: inBox && BoxWallpaper.IsWallpaperRed(sav.Version, sav.GetBoxWallpaper(box))); + bool inBox = (uint)slot < MaxSlotCount; + bool empty = pk.Species == 0; + var sprite = empty ? null : pk.Sprite(isBoxBGRed: inBox && BoxWallpaper.IsWallpaperRed(sav.Version, sav.GetBoxWallpaper(box))); - if (flagIllegal) + if (!empty && flagIllegal) { if (box >= 0) pk.Box = box; var la = new LegalityAnalysis(pk, sav.Personal); - if (!la.Valid && pk.Species != 0) - sprite = ImageUtil.LayerImage(sprite, Resources.warn, 0, 14); + if (!la.Valid) + sprite = ImageUtil.LayerImage(sprite, Resources.warn, 0, FlagIllegalShiftY); } if (inBox) // in box { var flags = sav.GetSlotFlags(box, slot); if (flags.HasFlagFast(StorageSlotFlag.Locked)) - sprite = ImageUtil.LayerImage(sprite, Resources.locked, 26, 0); + sprite = ImageUtil.LayerImage(sprite, Resources.locked, SlotLockShiftX, 0); int team = flags.IsBattleTeam(); if (team >= 0) - sprite = ImageUtil.LayerImage(sprite, Resources.team, 21, 0); + sprite = ImageUtil.LayerImage(sprite, Resources.team, SlotTeamShiftX, 0); int party = flags.IsParty(); if (party >= 0) - sprite = ImageUtil.LayerImage(sprite, PartyMarks[party], 24, 0); + sprite = ImageUtil.LayerImage(sprite, PartyMarks[party], PartyMarkShiftX, 0); if (flags.HasFlagFast(StorageSlotFlag.Starter)) sprite = ImageUtil.LayerImage(sprite, Resources.starter, 0, 0); } @@ -149,6 +151,14 @@ namespace PKHeX.WinForms return sprite; } + private const int MaxSlotCount = 30; // slots in a box + private const int SpriteWidth = 40; + private const int SpriteHeight = 30; + private const int PartyMarkShiftX = SpriteWidth - 16; + private const int SlotLockShiftX = SpriteWidth - 14; + private const int SlotTeamShiftX = SpriteWidth - 19; + private const int FlagIllegalShiftY = SpriteHeight - 16; + private static readonly Image[] PartyMarks = { Resources.party1, Resources.party2, Resources.party3, Resources.party4, Resources.party5, Resources.party6, @@ -156,26 +166,31 @@ namespace PKHeX.WinForms 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); + GetSpriteGlow(pk, bgr[0], bgr[1], bgr[2], out pixels, out baseSprite, forceHollow); } - public static void GetSpriteGlow(Image baseSprite, byte[] bgr, out byte[] pixels, bool forceHollow = false) + public static void GetSpriteGlow(PKM pk, byte blue, byte green, byte red, out byte[] pixels, out Image baseSprite, bool forceHollow = false) { - pixels = ImageUtil.GetPixelData((Bitmap) baseSprite); + bool egg = pk.IsEgg; + baseSprite = GetSprite(pk.Species, pk.AltForm, pk.Gender, 0, egg, false, pk.Format); + GetSpriteGlow(baseSprite, blue, green, red, out pixels, forceHollow || egg); + } + + public static void GetSpriteGlow(Image baseSprite, byte blue, byte green, byte red, out byte[] pixels, bool forceHollow = false) + { + pixels = ImageUtil.GetPixelData((Bitmap)baseSprite); if (!forceHollow) { - ImageUtil.GlowEdges(pixels, bgr, baseSprite.Width); + ImageUtil.GlowEdges(pixels, blue, green, red, 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(); + var original = (byte[])pixels.Clone(); ImageUtil.SetAllUsedPixelsOpaque(pixels); - ImageUtil.GlowEdges(pixels, bgr, baseSprite.Width); + ImageUtil.GlowEdges(pixels, blue, green, red, baseSprite.Width); ImageUtil.RemovePixels(pixels, original); }