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
This commit is contained in:
Kurt 2019-04-22 22:24:29 -07:00
parent 963cb1aa99
commit 9861128b63
4 changed files with 84 additions and 53 deletions

View file

@ -78,8 +78,7 @@ namespace PKHeX.WinForms.Controls
{ {
HoverWorker.Stop(); HoverWorker.Stop();
var bgr = new[] { Draw.GlowInitial.B, Draw.GlowInitial.G, Draw.GlowInitial.R }; SpriteUtil.GetSpriteGlow(pk, Draw.GlowInitial.B, Draw.GlowInitial.G, Draw.GlowInitial.R, out var glowdata, out var GlowBase);
SpriteUtil.GetSpriteGlow(pk, bgr, out var glowdata, out var GlowBase);
hover = ImageUtil.LayerImage(GlowBase, Resources.slotHover, 0, 0); hover = ImageUtil.LayerImage(GlowBase, Resources.slotHover, 0, 0);
HoverWorker.GlowToColor = Draw.GlowFinal; HoverWorker.GlowToColor = Draw.GlowFinal;
HoverWorker.GlowFromColor = Draw.GlowInitial; HoverWorker.GlowFromColor = Draw.GlowInitial;

View file

@ -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 stride = width * 4;
int height = data.Length / stride; int height = data.Length / stride;
for (int i = 0; i < data.Length; i += 4) for (int i = 0; i < data.Length; i += 4)
{ {
// only pollute outwards if the current pixel isn't transparent
if (data[i + 3] == 0) if (data[i + 3] == 0)
continue; continue;
@ -189,23 +195,32 @@ namespace PKHeX.WinForms
{ {
for (int j = top; j <= bottom; j++) 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)); var c = 4 * (i + (j * width));
data[c + 0] += (byte)(amount * (0xFF - data[c + 0])); 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) for (int i = 0; i < data.Length; i += 4)
{ {
// only clean if the current pixel isn't transparent
if (data[i + 3] != 0) if (data[i + 3] != 0)
continue; 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; continue;
data[i + 3] = flair; data[i + 0] = blue;
data[i + 0] = colors[0]; data[i + 1] = green;
data[i + 1] = colors[1]; data[i + 2] = red;
data[i + 2] = colors[2]; data[i + 3] = transparency;
} }
} }

View file

@ -8,6 +8,15 @@ namespace PKHeX.WinForms
{ {
public static bool ShowEggSpriteAsItem { get; set; } = true; 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) public void Initialize(SaveFile sav)
{ {
if (sav.Generation != 3) 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) private static Image GetBaseImageTotem(int species, int form, int gender, bool shiny, int generation)
{ {
var baseform = FormConverter.GetTotemBaseForm(species, form); var baseform = FormConverter.GetTotemBaseForm(species, form);
var file = PKX.GetResourceStringSprite(species, baseform, gender, generation, shiny); var baseImage = GetBaseImageDefault(species, baseform, gender, shiny, generation);
var baseImage = (Image)Resources.ResourceManager.GetObject(file);
return ImageUtil.ToGrayscale(baseImage); 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) private static Image GetBaseImageFallback(int species, int form, int gender, bool shiny, int generation)
{ {
Image baseImage;
if (shiny) // try again without shiny if (shiny) // try again without shiny
{ {
var file = PKX.GetResourceStringSprite(species, form, gender, generation); var img = GetBaseImageDefault(species, form, gender, false, generation);
baseImage = (Image)Resources.ResourceManager.GetObject(file); if (img != null)
if (baseImage != null) return img;
return baseImage;
} }
// try again without form // try again without form
baseImage = (Image)Resources.ResourceManager.GetObject($"_{species}"); var baseImage = (Image)Resources.ResourceManager.GetObject($"_{species}");
if (baseImage == null) // failed again if (baseImage == null) // failed again
return Resources.unknown; 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) 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 if (generation >= 2 && generation <= 4 && 328 <= item && item <= 419) // gen2/3/4 TM
itemimg = Resources.item_tm; itemimg = Resources.item_tm;
// Redraw // Redraw item in bottom right corner; since images are cropped, try to not have them at the edge
int x = 22 + ((15 - itemimg.Width) / 2); int x = ItemShiftX + ((ItemMaxSize - itemimg.Width) / 2);
if (x + itemimg.Width > baseImage.Width) if (x + itemimg.Width > baseImage.Width)
x = baseImage.Width - itemimg.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); return ImageUtil.LayerImage(baseImage, itemimg, x, y);
} }
@ -113,7 +119,7 @@ namespace PKHeX.WinForms
{ {
// Add shiny star to top left of image. // Add shiny star to top left of image.
var rare = isBoxBGRed ? Resources.rare_icon_alt : Resources.rare_icon; 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) 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 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) private static Image LayerOverImageEggTransparentSpecies(Image baseImage, int species)
{ {
// Partially transparent species. // Partially transparent species.
@ -141,7 +143,7 @@ namespace PKHeX.WinForms
private static Image LayerOverImageEggAsItem(Image baseImage, int species) private static Image LayerOverImageEggAsItem(Image baseImage, int species)
{ {
var egg = GetEggSprite(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
} }
} }
} }

View file

@ -10,8 +10,8 @@ namespace PKHeX.WinForms
public static Image GetBallSprite(int ball) public static Image GetBallSprite(int ball)
{ {
string str = PKX.GetResourceStringBall(ball); string resource = PKX.GetResourceStringBall(ball);
return (Image)Resources.ResourceManager.GetObject(str) ?? Resources._ball4; // Poké Ball (default) 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) 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) public static Image GetRibbonSprite(string name)
{ {
var sprite = name.Replace("CountG3", "G3").ToLower(); var resource = name.Replace("CountG3", "G3").ToLower();
return Resources.ResourceManager.GetObject(sprite) as Image; return (Bitmap)Resources.ResourceManager.GetObject(resource);
} }
public static Image GetRibbonSprite(string name, int max, int value) public static Image GetRibbonSprite(string name, int max, int value)
@ -57,7 +57,7 @@ namespace PKHeX.WinForms
{ {
if (generation <= 2) if (generation <= 2)
type = (int)((MoveType)type).GetMoveTypeGeneration(generation); 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) private static Image GetSprite(MysteryGift gift)
@ -82,7 +82,7 @@ namespace PKHeX.WinForms
int item = gift.ItemID; int item = gift.ItemID;
if (Legal.ZCrystalDictionary.TryGetValue(item, out int value)) if (Legal.ZCrystalDictionary.TryGetValue(item, out int value))
item = 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; 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); 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 is IShadowPKM s && s.Purification > 0)
{ {
if (pk.Species == 249) // Lugia const int Lugia = 249;
img = Spriter.GetSprite(Resources._249x, 249, pk.HeldItem, pk.IsEgg, pk.IsShiny, pk.Format, isBoxBGRed); if (pk.Species == Lugia) // show XD shadow sprite
GetSpriteGlow(pk, new byte[] { 75, 0, 130 }, out var pixels, out var baseSprite, true); 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); var glowImg = ImageUtil.GetBitmap(pixels, baseSprite.Width, baseSprite.Height, baseSprite.PixelFormat);
img = ImageUtil.LayerImage(glowImg, img, 0, 0); img = ImageUtil.LayerImage(glowImg, img, 0, 0);
} }
@ -120,28 +121,29 @@ namespace PKHeX.WinForms
if (!pk.Valid) if (!pk.Valid)
return null; return null;
bool inBox = slot >= 0 && slot < 30; bool inBox = (uint)slot < MaxSlotCount;
var sprite = pk.Species == 0 ? null : pk.Sprite(isBoxBGRed: inBox && BoxWallpaper.IsWallpaperRed(sav.Version, sav.GetBoxWallpaper(box))); 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) if (box >= 0)
pk.Box = box; pk.Box = box;
var la = new LegalityAnalysis(pk, sav.Personal); var la = new LegalityAnalysis(pk, sav.Personal);
if (!la.Valid && pk.Species != 0) if (!la.Valid)
sprite = ImageUtil.LayerImage(sprite, Resources.warn, 0, 14); sprite = ImageUtil.LayerImage(sprite, Resources.warn, 0, FlagIllegalShiftY);
} }
if (inBox) // in box if (inBox) // in box
{ {
var flags = sav.GetSlotFlags(box, slot); var flags = sav.GetSlotFlags(box, slot);
if (flags.HasFlagFast(StorageSlotFlag.Locked)) 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(); int team = flags.IsBattleTeam();
if (team >= 0) if (team >= 0)
sprite = ImageUtil.LayerImage(sprite, Resources.team, 21, 0); sprite = ImageUtil.LayerImage(sprite, Resources.team, SlotTeamShiftX, 0);
int party = flags.IsParty(); int party = flags.IsParty();
if (party >= 0) if (party >= 0)
sprite = ImageUtil.LayerImage(sprite, PartyMarks[party], 24, 0); sprite = ImageUtil.LayerImage(sprite, PartyMarks[party], PartyMarkShiftX, 0);
if (flags.HasFlagFast(StorageSlotFlag.Starter)) if (flags.HasFlagFast(StorageSlotFlag.Starter))
sprite = ImageUtil.LayerImage(sprite, Resources.starter, 0, 0); sprite = ImageUtil.LayerImage(sprite, Resources.starter, 0, 0);
} }
@ -149,6 +151,14 @@ namespace PKHeX.WinForms
return sprite; 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 = private static readonly Image[] PartyMarks =
{ {
Resources.party1, Resources.party2, Resources.party3, Resources.party4, Resources.party5, Resources.party6, Resources.party1, Resources.party2, Resources.party3, Resources.party4, Resources.party5, Resources.party6,
@ -156,17 +166,22 @@ namespace PKHeX.WinForms
public static void GetSpriteGlow(PKM pk, byte[] bgr, out byte[] pixels, out Image baseSprite, bool forceHollow = false) public static void GetSpriteGlow(PKM pk, byte[] bgr, out byte[] pixels, out Image baseSprite, bool forceHollow = false)
{ {
bool egg = pk.IsEgg; GetSpriteGlow(pk, bgr[0], bgr[1], bgr[2], out pixels, out baseSprite, forceHollow);
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) public static void GetSpriteGlow(PKM pk, byte blue, byte green, byte red, 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, 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); pixels = ImageUtil.GetPixelData((Bitmap)baseSprite);
if (!forceHollow) if (!forceHollow)
{ {
ImageUtil.GlowEdges(pixels, bgr, baseSprite.Width); ImageUtil.GlowEdges(pixels, blue, green, red, baseSprite.Width);
return; return;
} }
@ -175,7 +190,7 @@ namespace PKHeX.WinForms
// Remove opaque pixels from original image, leaving only the glow effect pixels. // 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.SetAllUsedPixelsOpaque(pixels);
ImageUtil.GlowEdges(pixels, bgr, baseSprite.Width); ImageUtil.GlowEdges(pixels, blue, green, red, baseSprite.Width);
ImageUtil.RemovePixels(pixels, original); ImageUtil.RemovePixels(pixels, original);
} }