File scoped namespace for drawing projects

Extract color util to separate class as it's not entirely image related.
This commit is contained in:
Kurt 2021-12-10 00:15:04 -08:00
parent 9f920405df
commit aa968f56aa
25 changed files with 1095 additions and 1126 deletions

View file

@ -53,9 +53,7 @@ public static class ContestStatInfo
return initial.CNT_Sheen;
if (pokeBlock3)
{
return CalculateMaximumSheen3(s, nature, initial);
}
var avg = GetAverageFeel(s, nature, initial);
if (avg <= 0)
@ -162,7 +160,7 @@ public static class ContestStatInfo
private static readonly sbyte[] NatureAmpTable =
{
// Spicy, Dry, Sweet, Bitter, Sour
// Spicy, Dry, Sweet, Bitter, Sour
0, 0, 0, 0, 0, // Hardy
1, 0, 0, 0,-1, // Lonely
1, 0,-1, 0, 0, // Brave

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net6.0;net46</TargetFrameworks>
<LangVersion>9</LangVersion>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

View file

@ -5,104 +5,99 @@ using System.Text;
using System.Text.RegularExpressions;
using PKHeX.Core;
namespace PKHeX.Drawing.Misc
namespace PKHeX.Drawing.Misc;
public static class QRDecode
{
public static class QRDecode
// QR Utility
private const string DecodeAPI = "http://api.qrserver.com/v1/read-qr-code/?fileurl=";
public static QRDecodeResult GetQRData(string address, out byte[] result)
{
// QR Utility
private const string DecodeAPI = "http://api.qrserver.com/v1/read-qr-code/?fileurl=";
result = Array.Empty<byte>();
// Fetch data from QR code...
public static QRDecodeResult GetQRData(string address, out byte[] result)
if (!address.StartsWith("http"))
return QRDecodeResult.BadPath;
string url = DecodeAPI + WebUtility.UrlEncode(address);
string data;
try
{
result = Array.Empty<byte>();
// Fetch data from QR code...
var str = NetUtil.GetStringFromURL(url);
if (str is null)
return QRDecodeResult.BadConnection;
if (!address.StartsWith("http"))
return QRDecodeResult.BadPath;
data = str;
if (data.Contains("could not find"))
return QRDecodeResult.BadImage;
string url = DecodeAPI + WebUtility.UrlEncode(address);
string data;
try
{
var str = NetUtil.GetStringFromURL(url);
if (str is null)
return QRDecodeResult.BadConnection;
data = str;
if (data.Contains("could not find"))
return QRDecodeResult.BadImage;
if (data.Contains("filetype not supported"))
return QRDecodeResult.BadType;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch { return QRDecodeResult.BadConnection; }
#pragma warning restore CA1031 // Do not catch general exception types
// Quickly convert the json response to a data string
try
{
result = DecodeQRJson(data);
return QRDecodeResult.Success;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception e)
#pragma warning restore CA1031 // Do not catch general exception types
{
Debug.WriteLine(e.Message);
return QRDecodeResult.BadConversion;
}
if (data.Contains("filetype not supported"))
return QRDecodeResult.BadType;
}
catch { return QRDecodeResult.BadConnection; }
private static byte[] DecodeQRJson(string data)
// Quickly convert the json response to a data string
try
{
const string cap = "\",\"error\":null}]}]";
const string intro = "[{\"type\":\"qrcode\",\"symbol\":[{\"seq\":0,\"data\":\"";
const string qrcode = "nQR-Code:";
if (!data.StartsWith(intro))
throw new FormatException();
string pkstr = data[intro.Length..];
// Remove multiple QR codes in same image
var qr = pkstr.IndexOf(qrcode, StringComparison.Ordinal);
if (qr != -1)
pkstr = pkstr[..qr];
// Trim outro
var outroIndex = pkstr.IndexOf(cap, StringComparison.Ordinal);
if (outroIndex == -1)
throw new FormatException();
pkstr = pkstr[..outroIndex];
if (!pkstr.StartsWith("http") && !pkstr.StartsWith("null")) // G7
{
string fstr = Regex.Unescape(pkstr);
byte[] raw = Encoding.Unicode.GetBytes(fstr);
// Remove 00 interstitials and retrieve from offset 0x30, take PK7 Stored Size (always)
byte[] result = new byte[0xE8];
for (int i = 0; i < result.Length; i++)
result[i] = raw[(i + 0x30) * 2];
return result;
}
// All except G7
pkstr = pkstr[(pkstr.IndexOf('#') + 1)..]; // Trim URL
pkstr = pkstr.Replace("\\", string.Empty); // Rectify response
return Convert.FromBase64String(pkstr);
result = DecodeQRJson(data);
return QRDecodeResult.Success;
}
public static string ConvertMsg(this QRDecodeResult result) => result switch
catch (Exception e)
{
QRDecodeResult.Success => string.Empty,
QRDecodeResult.BadPath => MessageStrings.MsgQRUrlFailPath,
QRDecodeResult.BadImage => MessageStrings.MsgQRUrlFailImage,
QRDecodeResult.BadType => MessageStrings.MsgQRUrlFailType,
QRDecodeResult.BadConnection => MessageStrings.MsgQRUrlFailConnection,
QRDecodeResult.BadConversion => MessageStrings.MsgQRUrlFailConvert,
_ => throw new ArgumentOutOfRangeException(nameof(result), result, null),
};
Debug.WriteLine(e.Message);
return QRDecodeResult.BadConversion;
}
}
private static byte[] DecodeQRJson(string data)
{
const string cap = "\",\"error\":null}]}]";
const string intro = "[{\"type\":\"qrcode\",\"symbol\":[{\"seq\":0,\"data\":\"";
const string qrcode = "nQR-Code:";
if (!data.StartsWith(intro))
throw new FormatException();
string pkstr = data[intro.Length..];
// Remove multiple QR codes in same image
var qr = pkstr.IndexOf(qrcode, StringComparison.Ordinal);
if (qr != -1)
pkstr = pkstr[..qr];
// Trim outro
var outroIndex = pkstr.IndexOf(cap, StringComparison.Ordinal);
if (outroIndex == -1)
throw new FormatException();
pkstr = pkstr[..outroIndex];
if (!pkstr.StartsWith("http") && !pkstr.StartsWith("null")) // G7
{
string fstr = Regex.Unescape(pkstr);
byte[] raw = Encoding.Unicode.GetBytes(fstr);
// Remove 00 interstitials and retrieve from offset 0x30, take PK7 Stored Size (always)
byte[] result = new byte[0xE8];
for (int i = 0; i < result.Length; i++)
result[i] = raw[(i + 0x30) * 2];
return result;
}
// All except G7
pkstr = pkstr[(pkstr.IndexOf('#') + 1)..]; // Trim URL
pkstr = pkstr.Replace("\\", string.Empty); // Rectify response
return Convert.FromBase64String(pkstr);
}
public static string ConvertMsg(this QRDecodeResult result) => result switch
{
QRDecodeResult.Success => string.Empty,
QRDecodeResult.BadPath => MessageStrings.MsgQRUrlFailPath,
QRDecodeResult.BadImage => MessageStrings.MsgQRUrlFailImage,
QRDecodeResult.BadType => MessageStrings.MsgQRUrlFailType,
QRDecodeResult.BadConnection => MessageStrings.MsgQRUrlFailConnection,
QRDecodeResult.BadConversion => MessageStrings.MsgQRUrlFailConvert,
_ => throw new ArgumentOutOfRangeException(nameof(result), result, null),
};
}

View file

@ -1,12 +1,11 @@
namespace PKHeX.Drawing.Misc
namespace PKHeX.Drawing.Misc;
public enum QRDecodeResult
{
public enum QRDecodeResult
{
Success,
BadPath,
BadImage,
BadType,
BadConnection,
BadConversion,
}
}
Success,
BadPath,
BadImage,
BadType,
BadConnection,
BadConversion,
}

View file

@ -2,26 +2,25 @@
using PKHeX.Core;
using QRCoder;
namespace PKHeX.Drawing.Misc
namespace PKHeX.Drawing.Misc;
public static class QREncode
{
public static class QREncode
public static Image GenerateQRCode(DataMysteryGift mg) => GenerateQRCode(QRMessageUtil.GetMessage(mg));
public static Image GenerateQRCode(PKM pkm) => GenerateQRCode(QRMessageUtil.GetMessage(pkm));
public static Image GenerateQRCode7(PK7 pk7, int box = 0, int slot = 0, int copies = 1)
{
public static Image GenerateQRCode(DataMysteryGift mg) => GenerateQRCode(QRMessageUtil.GetMessage(mg));
public static Image GenerateQRCode(PKM pkm) => GenerateQRCode(QRMessageUtil.GetMessage(pkm));
public static Image GenerateQRCode7(PK7 pk7, int box = 0, int slot = 0, int copies = 1)
{
byte[] data = QR7.GenerateQRData(pk7, box, slot, copies);
var msg = QRMessageUtil.GetMessage(data);
return GenerateQRCode(msg, ppm: 4);
}
private static Image GenerateQRCode(string msg, int ppm = 4)
{
using var generator = new QRCodeGenerator();
using var data = generator.CreateQrCode(msg, QRCodeGenerator.ECCLevel.Q);
using var code = new QRCode(data);
return code.GetGraphic(ppm);
}
byte[] data = QR7.GenerateQRData(pk7, box, slot, copies);
var msg = QRMessageUtil.GetMessage(data);
return GenerateQRCode(msg, ppm: 4);
}
}
private static Image GenerateQRCode(string msg, int ppm = 4)
{
using var generator = new QRCodeGenerator();
using var data = generator.CreateQrCode(msg, QRCodeGenerator.ECCLevel.Q);
using var code = new QRCode(data);
return code.GetGraphic(ppm);
}
}

View file

@ -1,51 +1,50 @@
using System;
using System.Drawing;
namespace PKHeX.Drawing.Misc
namespace PKHeX.Drawing.Misc;
public static class QRImageUtil
{
public static class QRImageUtil
public static Bitmap GetQRImage(Image qr, Image preview)
{
public static Bitmap GetQRImage(Image qr, Image preview)
// create a small area with the pkm sprite, with a white background
var foreground = new Bitmap(preview.Width + 4, preview.Height + 4);
using (Graphics gfx = Graphics.FromImage(foreground))
{
// create a small area with the pkm sprite, with a white background
var foreground = new Bitmap(preview.Width + 4, preview.Height + 4);
using (Graphics gfx = Graphics.FromImage(foreground))
{
gfx.FillRectangle(new SolidBrush(Color.White), 0, 0, foreground.Width, foreground.Height);
int x = (foreground.Width / 2) - (preview.Width / 2);
int y = (foreground.Height / 2) - (preview.Height / 2);
gfx.DrawImage(preview, x, y);
}
// Layer on Preview Image
{
int x = (qr.Width / 2) - (foreground.Width / 2);
int y = (qr.Height / 2) - (foreground.Height / 2);
return ImageUtil.LayerImage(qr, foreground, x, y);
}
gfx.FillRectangle(new SolidBrush(Color.White), 0, 0, foreground.Width, foreground.Height);
int x = (foreground.Width / 2) - (preview.Width / 2);
int y = (foreground.Height / 2) - (preview.Height / 2);
gfx.DrawImage(preview, x, y);
}
public static Bitmap GetQRImageExtended(Font font, Image qr, Image pkm, int width, int height, string[] lines, string extraText)
// Layer on Preview Image
{
var pic = GetQRImage(qr, pkm);
return ExtendImage(font, qr, width, height, pic, lines, extraText);
int x = (qr.Width / 2) - (foreground.Width / 2);
int y = (qr.Height / 2) - (foreground.Height / 2);
return ImageUtil.LayerImage(qr, foreground, x, y);
}
private static Bitmap ExtendImage(Font font, Image qr, int width, int height, Image pic, string[] lines, string extraText)
{
var newpic = new Bitmap(width, height);
using Graphics g = Graphics.FromImage(newpic);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, newpic.Width, newpic.Height);
g.DrawImage(pic, 0, 0);
g.DrawString(GetLine(lines, 0), font, Brushes.Black, new PointF(18, qr.Height - 5));
g.DrawString(GetLine(lines, 1), font, Brushes.Black, new PointF(18, qr.Height + 8));
g.DrawString(GetLine(lines, 2).Replace(Environment.NewLine, "/").Replace("//", " ").Replace(":/", ": "), font,
Brushes.Black, new PointF(18, qr.Height + 20));
g.DrawString(GetLine(lines, 3) + extraText, font, Brushes.Black, new PointF(18, qr.Height + 32));
return newpic;
}
private static string GetLine(string[] lines, int line) => lines.Length <= line ? string.Empty : lines[line];
}
public static Bitmap GetQRImageExtended(Font font, Image qr, Image pkm, int width, int height, string[] lines, string extraText)
{
var pic = GetQRImage(qr, pkm);
return ExtendImage(font, qr, width, height, pic, lines, extraText);
}
private static Bitmap ExtendImage(Font font, Image qr, int width, int height, Image pic, string[] lines, string extraText)
{
var newpic = new Bitmap(width, height);
using Graphics g = Graphics.FromImage(newpic);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, newpic.Width, newpic.Height);
g.DrawImage(pic, 0, 0);
g.DrawString(GetLine(lines, 0), font, Brushes.Black, new PointF(18, qr.Height - 5));
g.DrawString(GetLine(lines, 1), font, Brushes.Black, new PointF(18, qr.Height + 8));
g.DrawString(GetLine(lines, 2).Replace(Environment.NewLine, "/").Replace("//", " ").Replace(":/", ": "), font,
Brushes.Black, new PointF(18, qr.Height + 20));
g.DrawString(GetLine(lines, 3) + extraText, font, Brushes.Black, new PointF(18, qr.Height + 32));
return newpic;
}
private static string GetLine(string[] lines, int line) => lines.Length <= line ? string.Empty : lines[line];
}

View file

@ -3,40 +3,39 @@ using PKHeX.Core;
using PKHeX.Drawing.Misc.Properties;
using PKHeX.Drawing.PokeSprite;
namespace PKHeX.Drawing.Misc
namespace PKHeX.Drawing.Misc;
public static class MysteryGiftSpriteUtil
{
public static class MysteryGiftSpriteUtil
public static Image Sprite(this MysteryGift gift) => GetSprite(gift);
private static Image GetSprite(MysteryGift gift)
{
public static Image Sprite(this MysteryGift gift) => GetSprite(gift);
if (gift.Empty)
return SpriteUtil.Spriter.None;
private static Image GetSprite(MysteryGift gift)
var img = GetBaseImage(gift);
if (SpriteBuilder.ShowEncounterColor != SpriteBackgroundType.None)
img = SpriteUtil.ApplyEncounterColor(gift, img, SpriteBuilder.ShowEncounterColor);
if (gift.GiftUsed)
img = ImageUtil.ChangeOpacity(img, 0.3);
return img;
}
private static Image GetBaseImage(MysteryGift gift)
{
if (gift.IsEgg && gift.Species == (int)Species.Manaphy) // Manaphy Egg
return SpriteUtil.GetMysteryGiftPreviewPoke(gift);
if (gift.IsPokémon)
return SpriteUtil.GetMysteryGiftPreviewPoke(gift);
if (gift.IsItem)
{
if (gift.Empty)
return SpriteUtil.Spriter.None;
var img = GetBaseImage(gift);
if (SpriteBuilder.ShowEncounterColor != SpriteBackgroundType.None)
img = SpriteUtil.ApplyEncounterColor(gift, img, SpriteBuilder.ShowEncounterColor);
if (gift.GiftUsed)
img = ImageUtil.ChangeOpacity(img, 0.3);
return img;
}
private static Image GetBaseImage(MysteryGift gift)
{
if (gift.IsEgg && gift.Species == (int)Species.Manaphy) // Manaphy Egg
return SpriteUtil.GetMysteryGiftPreviewPoke(gift);
if (gift.IsPokémon)
return SpriteUtil.GetMysteryGiftPreviewPoke(gift);
if (gift.IsItem)
{
int item = gift.ItemID;
if (Legal.ZCrystalDictionary.TryGetValue(item, out int value))
item = value;
return SpriteUtil.GetItemSprite(item) ?? Resources.Bag_Key;
}
return PokeSprite.Properties.Resources.b_unknown;
int item = gift.ItemID;
if (Legal.ZCrystalDictionary.TryGetValue(item, out int value))
item = value;
return SpriteUtil.GetItemSprite(item) ?? Resources.Bag_Key;
}
return PokeSprite.Properties.Resources.b_unknown;
}
}

View file

@ -2,20 +2,19 @@
using PKHeX.Core;
using PKHeX.Drawing.Misc.Properties;
namespace PKHeX.Drawing.Misc
{
public static class PlayerSpriteUtil
{
public static Image? Sprite(this SaveFile sav) => GetSprite(sav);
namespace PKHeX.Drawing.Misc;
private static Image? GetSprite(SaveFile sav)
public static class PlayerSpriteUtil
{
public static Image? Sprite(this SaveFile sav) => GetSprite(sav);
private static Image? GetSprite(SaveFile sav)
{
if (sav is SAV6XY or SAV6AO)
{
if (sav is SAV6XY or SAV6AO)
{
string file = $"tr_{sav.MultiplayerSpriteID:00}";
return Resources.ResourceManager.GetObject(file) as Image ?? Resources.tr_00;
}
return null;
string file = $"tr_{sav.MultiplayerSpriteID:00}";
return Resources.ResourceManager.GetObject(file) as Image ?? Resources.tr_00;
}
return null;
}
}

View file

@ -1,41 +1,40 @@
using System.Drawing;
using PKHeX.Drawing.Misc.Properties;
namespace PKHeX.Drawing.Misc
namespace PKHeX.Drawing.Misc;
public static class RibbonSpriteUtil
{
public static class RibbonSpriteUtil
public static Image? GetRibbonSprite(string name)
{
public static Image? GetRibbonSprite(string name)
var resource = name.Replace("CountG3", "G3").ToLowerInvariant();
return (Bitmap?)Resources.ResourceManager.GetObject(resource);
}
public static Image? GetRibbonSprite(string name, int max, int value)
{
var resource = GetRibbonSpriteName(name, max, value);
return (Bitmap?)Resources.ResourceManager.GetObject(resource);
}
private static string GetRibbonSpriteName(string name, int max, int value)
{
if (max != 4) // Memory
{
var resource = name.Replace("CountG3", "G3").ToLowerInvariant();
return (Bitmap?)Resources.ResourceManager.GetObject(resource);
var sprite = name.ToLowerInvariant();
if (max == value)
return sprite + "2";
return sprite;
}
public static Image? GetRibbonSprite(string name, int max, int value)
// Count ribbons
string n = name.Replace("Count", string.Empty).ToLowerInvariant();
return value switch
{
var resource = GetRibbonSpriteName(name, max, value);
return (Bitmap?)Resources.ResourceManager.GetObject(resource);
}
private static string GetRibbonSpriteName(string name, int max, int value)
{
if (max != 4) // Memory
{
var sprite = name.ToLowerInvariant();
if (max == value)
return sprite + "2";
return sprite;
}
// Count ribbons
string n = name.Replace("Count", string.Empty).ToLowerInvariant();
return value switch
{
2 => n + "super",
3 => n + "hyper",
4 => n + "master",
_ => n,
};
}
2 => n + "super",
3 => n + "hyper",
4 => n + "master",
_ => n,
};
}
}

View file

@ -2,15 +2,14 @@
using PKHeX.Core;
using PKHeX.Drawing.Misc.Properties;
namespace PKHeX.Drawing.Misc
namespace PKHeX.Drawing.Misc;
public static class TypeSpriteUtil
{
public static class TypeSpriteUtil
public static Image? GetTypeSprite(int type, int generation = PKX.Generation)
{
public static Image? GetTypeSprite(int type, int generation = PKX.Generation)
{
if (generation <= 2)
type = (int)((MoveType)type).GetMoveTypeGeneration(generation);
return (Bitmap?)Resources.ResourceManager.GetObject($"type_icon_{type:00}");
}
if (generation <= 2)
type = (int)((MoveType)type).GetMoveTypeGeneration(generation);
return (Bitmap?)Resources.ResourceManager.GetObject($"type_icon_{type:00}");
}
}

View file

@ -3,40 +3,39 @@ using PKHeX.Core;
using PKHeX.Drawing.Misc.Properties;
using static PKHeX.Core.GameVersion;
namespace PKHeX.Drawing.Misc
namespace PKHeX.Drawing.Misc;
public static class WallpaperUtil
{
public static class WallpaperUtil
public static Image WallpaperImage(this SaveFile sav, int box) => GetWallpaper(sav, box);
private static Image GetWallpaper(SaveFile sav, int box)
{
public static Image WallpaperImage(this SaveFile sav, int box) => GetWallpaper(sav, box);
private static Image GetWallpaper(SaveFile sav, int box)
{
string s = GetWallpaperResourceName(sav.Version, sav.GetBoxWallpaper(box));
return (Bitmap?)Resources.ResourceManager.GetObject(s) ?? Resources.box_wp16xy;
}
public static string GetWallpaperResourceName(GameVersion version, int index)
{
index++; // start indexes at 1
var suffix = GetResourceSuffix(version, index);
return $"box_wp{index:00}{suffix}";
}
private static string GetResourceSuffix(GameVersion version, int index) => version.GetGeneration() switch
{
3 when version == E => "e",
3 when FRLG.Contains(version) && index > 12 => "frlg",
3 => "rs",
4 when index < 16 => "dp",
4 when version == Pt => "pt",
4 when HGSS.Contains(version) => "hgss",
5 => B2W2.Contains(version) && index > 16 ? "b2w2" : "bw",
6 => ORAS.Contains(version) && index > 16 ? "ao" : "xy",
7 when !GG.Contains(version) => "xy",
8 => BDSP.Contains(version) ? "bdsp" : "swsh",
_ => string.Empty,
};
string s = GetWallpaperResourceName(sav.Version, sav.GetBoxWallpaper(box));
return (Bitmap?)Resources.ResourceManager.GetObject(s) ?? Resources.box_wp16xy;
}
public static string GetWallpaperResourceName(GameVersion version, int index)
{
index++; // start indexes at 1
var suffix = GetResourceSuffix(version, index);
return $"box_wp{index:00}{suffix}";
}
private static string GetResourceSuffix(GameVersion version, int index) => version.GetGeneration() switch
{
3 when version == E => "e",
3 when FRLG.Contains(version) && index > 12 => "frlg",
3 => "rs",
4 when index < 16 => "dp",
4 when version == Pt => "pt",
4 when HGSS.Contains(version) => "hgss",
5 => B2W2.Contains(version) && index > 16 ? "b2w2" : "bw",
6 => ORAS.Contains(version) && index > 16 ? "ao" : "xy",
7 when !GG.Contains(version) => "xy",
8 => BDSP.Contains(version) ? "bdsp" : "swsh",
_ => string.Empty,
};
}

View file

@ -1,15 +1,14 @@
namespace PKHeX.Drawing.PokeSprite
{
public interface ISpriteSettings
{
bool ShowEggSpriteAsHeldItem { get; set; }
bool ShowEncounterBall { get; set; }
namespace PKHeX.Drawing.PokeSprite;
SpriteBackgroundType ShowEncounterColor { get; set; }
SpriteBackgroundType ShowEncounterColorPKM { get; set; }
int ShowEncounterThicknessStripe { get; set; }
byte ShowEncounterOpacityBackground { get; set; }
byte ShowEncounterOpacityStripe { get; set; }
bool ShowExperiencePercent { get; set; }
}
public interface ISpriteSettings
{
bool ShowEggSpriteAsHeldItem { get; set; }
bool ShowEncounterBall { get; set; }
SpriteBackgroundType ShowEncounterColor { get; set; }
SpriteBackgroundType ShowEncounterColorPKM { get; set; }
int ShowEncounterThicknessStripe { get; set; }
byte ShowEncounterOpacityBackground { get; set; }
byte ShowEncounterOpacityStripe { get; set; }
bool ShowExperiencePercent { get; set; }
}

View file

@ -1,9 +1,8 @@
namespace PKHeX.Drawing.PokeSprite
namespace PKHeX.Drawing.PokeSprite;
public enum SpriteBackgroundType
{
public enum SpriteBackgroundType
{
None,
BottomStripe,
FullBackground,
}
None,
BottomStripe,
FullBackground,
}

View file

@ -2,207 +2,206 @@
using PKHeX.Core;
using PKHeX.Drawing.PokeSprite.Properties;
namespace PKHeX.Drawing.PokeSprite
namespace PKHeX.Drawing.PokeSprite;
public abstract class SpriteBuilder : ISpriteBuilder<Image>
{
public abstract class SpriteBuilder : ISpriteBuilder<Image>
public static bool ShowEggSpriteAsItem { get; set; } = true;
public static bool ShowEncounterBall { get; set; } = true;
public static SpriteBackgroundType ShowEncounterColor { get; set; } = SpriteBackgroundType.FullBackground;
public static SpriteBackgroundType ShowEncounterColorPKM { get; set; }
public static bool ShowExperiencePercent { get; set; }
public static byte ShowEncounterOpacityStripe { get; set; }
public static byte ShowEncounterOpacityBackground { get; set; }
public static int ShowEncounterThicknessStripe { get; set; }
/// <summary> Width of the generated Sprite image. </summary>
public abstract int Width { get; }
/// <summary> Height of the generated Sprite image. </summary>
public abstract int Height { get; }
/// <summary> Minimum amount of padding on the right side of the image when layering an item sprite. </summary>
protected abstract int ItemShiftX { get; }
/// <summary> Minimum amount of padding on the bottom side of the image when layering an item sprite. </summary>
protected abstract int ItemShiftY { get; }
/// <summary> Max width / height of an item image. </summary>
protected abstract int ItemMaxSize { get; }
protected abstract int EggItemShiftX { get; }
protected abstract int EggItemShiftY { get; }
public abstract Bitmap Hover { get; }
public abstract Bitmap View { get; }
public abstract Bitmap Set { get; }
public abstract Bitmap Delete { get; }
public abstract Bitmap Transparent { get; }
public abstract Bitmap Drag { get; }
public abstract Bitmap UnknownItem { get; }
public abstract Bitmap None { get; }
public abstract Bitmap ItemTM { get; }
public abstract Bitmap ItemTR { get; }
private const double UnknownFormTransparency = 0.5;
private const double ShinyTransparency = 0.7;
private const double EggUnderLayerTransparency = 0.33;
protected abstract string GetSpriteStringSpeciesOnly(int species);
protected abstract string GetSpriteAll(int species, int form, int gender, uint formarg, bool shiny, int generation);
protected abstract string GetItemResourceName(int item);
protected abstract Bitmap Unknown { get; }
protected abstract Bitmap GetEggSprite(int species);
public abstract Bitmap ShadowLugia { get; }
public void Initialize(SaveFile sav)
{
public static bool ShowEggSpriteAsItem { get; set; } = true;
public static bool ShowEncounterBall { get; set; } = true;
public static SpriteBackgroundType ShowEncounterColor { get; set; } = SpriteBackgroundType.FullBackground;
public static SpriteBackgroundType ShowEncounterColorPKM { get; set; }
public static bool ShowExperiencePercent { get; set; }
if (sav.Generation != 3)
return;
public static byte ShowEncounterOpacityStripe { get; set; }
public static byte ShowEncounterOpacityBackground { get; set; }
public static int ShowEncounterThicknessStripe { get; set; }
Game = sav.Version;
if (Game == GameVersion.FRLG)
Game = sav.Personal == PersonalTable.FR ? GameVersion.FR : GameVersion.LG;
}
/// <summary> Width of the generated Sprite image. </summary>
public abstract int Width { get; }
/// <summary> Height of the generated Sprite image. </summary>
public abstract int Height { get; }
private GameVersion Game;
/// <summary> Minimum amount of padding on the right side of the image when layering an item sprite. </summary>
protected abstract int ItemShiftX { get; }
/// <summary> Minimum amount of padding on the bottom side of the image when layering an item sprite. </summary>
protected abstract int ItemShiftY { get; }
/// <summary> Max width / height of an item image. </summary>
protected abstract int ItemMaxSize { get; }
private static int GetDeoxysForm(GameVersion game) => game switch
{
GameVersion.FR => 1, // Attack
GameVersion.LG => 2, // Defense
GameVersion.E => 3, // Speed
_ => 0,
};
protected abstract int EggItemShiftX { get; }
protected abstract int EggItemShiftY { get; }
private static int GetArceusForm4(int form) => form switch
{
> 9 => form - 1, // Realign to Gen5+ type indexes
9 => 999, // Curse, make it show as unrecognized form since we don't have a sprite.
_ => form,
};
public abstract Bitmap Hover { get; }
public abstract Bitmap View { get; }
public abstract Bitmap Set { get; }
public abstract Bitmap Delete { get; }
public abstract Bitmap Transparent { get; }
public abstract Bitmap Drag { get; }
public abstract Bitmap UnknownItem { get; }
public abstract Bitmap None { get; }
public abstract Bitmap ItemTM { get; }
public abstract Bitmap ItemTR { get; }
public Image GetSprite(int species, int form, int gender, uint formarg, int heldItem, bool isEgg, bool isShiny, int generation = -1, bool isBoxBGRed = false, bool isAltShiny = false)
{
if (species == 0)
return None;
private const double UnknownFormTransparency = 0.5;
private const double ShinyTransparency = 0.7;
private const double EggUnderLayerTransparency = 0.33;
if (generation == 3 && species == (int)Species.Deoxys) // Depends on Gen3 save file version
form = GetDeoxysForm(Game);
else if (generation == 4 && species == (int)Species.Arceus) // Curse type's existence in Gen4
form = GetArceusForm4(form);
protected abstract string GetSpriteStringSpeciesOnly(int species);
var baseImage = GetBaseImage(species, form, gender, formarg, isShiny, generation);
return GetSprite(baseImage, species, heldItem, isEgg, isShiny, generation, isBoxBGRed, isAltShiny);
}
protected abstract string GetSpriteAll(int species, int form, int gender, uint formarg, bool shiny, int generation);
protected abstract string GetItemResourceName(int item);
protected abstract Bitmap Unknown { get; }
protected abstract Bitmap GetEggSprite(int species);
public abstract Bitmap ShadowLugia { get; }
public Image GetSprite(Image baseSprite, int species, int heldItem, bool isEgg, bool isShiny, int generation = -1, bool isBoxBGRed = false, bool isAltShiny = false)
{
if (isEgg)
baseSprite = LayerOverImageEgg(baseSprite, species, heldItem != 0);
if (heldItem > 0)
baseSprite = LayerOverImageItem(baseSprite, heldItem, generation);
if (isShiny)
baseSprite = LayerOverImageShiny(baseSprite, isBoxBGRed, generation >= 8 && isAltShiny);
return baseSprite;
}
public void Initialize(SaveFile sav)
private Image GetBaseImage(int species, int form, int gender, uint formarg, bool shiny, int generation)
{
var img = FormInfo.IsTotemForm(species, form, generation)
? GetBaseImageTotem(species, form, gender, formarg, shiny, generation)
: GetBaseImageDefault(species, form, gender, formarg, shiny, generation);
return img ?? GetBaseImageFallback(species, form, gender, formarg, shiny, generation);
}
private Image? GetBaseImageTotem(int species, int form, int gender, uint formarg, bool shiny, int generation)
{
var baseform = FormInfo.GetTotemBaseForm(species, form);
var baseImage = GetBaseImageDefault(species, baseform, gender, formarg, shiny, generation);
if (baseImage == null)
return null;
return ImageUtil.ToGrayscale(baseImage);
}
private Image? GetBaseImageDefault(int species, int form, int gender, uint formarg, bool shiny, int generation)
{
var file = GetSpriteAll(species, form, gender, formarg, shiny, generation);
return (Image?)Resources.ResourceManager.GetObject(file);
}
private Image GetBaseImageFallback(int species, int form, int gender, uint formarg, bool shiny, int generation)
{
if (shiny) // try again without shiny
{
if (sav.Generation != 3)
return;
Game = sav.Version;
if (Game == GameVersion.FRLG)
Game = sav.Personal == PersonalTable.FR ? GameVersion.FR : GameVersion.LG;
var img = GetBaseImageDefault(species, form, gender, formarg, false, generation);
if (img != null)
return img;
}
private GameVersion Game;
// try again without form
var baseImage = (Image?)Resources.ResourceManager.GetObject(GetSpriteStringSpeciesOnly(species));
if (baseImage == null) // failed again
return Unknown;
return ImageUtil.LayerImage(baseImage, Unknown, 0, 0, UnknownFormTransparency);
}
private static int GetDeoxysForm(GameVersion game) => game switch
private Image LayerOverImageItem(Image baseImage, int item, int generation)
{
Image itemimg = generation switch
{
GameVersion.FR => 1, // Attack
GameVersion.LG => 2, // Defense
GameVersion.E => 3, // Speed
_ => 0,
<= 4 when item is >= 328 and <= 419 => ItemTM, // gen2/3/4 TM
8 when item is >= 328 and <= 427 => ItemTM, // BDSP TMs
>= 8 when item is >= 1130 and <= 1229 => ItemTR, // Gen8 TR
_ => (Image?)Resources.ResourceManager.GetObject(GetItemResourceName(item)) ?? UnknownItem,
};
private static int GetArceusForm4(int form) => form switch
{
> 9 => form - 1, // Realign to Gen5+ type indexes
9 => 999, // Curse, make it show as unrecognized form since we don't have a sprite.
_ => form,
};
// Redraw item in bottom right corner; since images are cropped, try to not have them at the edge
int x = baseImage.Width - itemimg.Width - ((ItemMaxSize - itemimg.Width) / 4) - ItemShiftX;
int y = baseImage.Height - itemimg.Height - ItemShiftY;
return ImageUtil.LayerImage(baseImage, itemimg, x, y);
}
public Image GetSprite(int species, int form, int gender, uint formarg, int heldItem, bool isEgg, bool isShiny, int generation = -1, bool isBoxBGRed = false, bool isAltShiny = false)
{
if (species == 0)
return None;
private static Image LayerOverImageShiny(Image baseImage, bool isBoxBGRed, bool altShiny)
{
// Add shiny star to top left of image.
var rare = isBoxBGRed ? Resources.rare_icon_alt : Resources.rare_icon;
if (altShiny)
rare = Resources.rare_icon_2;
return ImageUtil.LayerImage(baseImage, rare, 0, 0, ShinyTransparency);
}
if (generation == 3 && species == (int)Species.Deoxys) // Depends on Gen3 save file version
form = GetDeoxysForm(Game);
else if (generation == 4 && species == (int)Species.Arceus) // Curse type's existence in Gen4
form = GetArceusForm4(form);
private Image LayerOverImageEgg(Image baseImage, int species, bool hasItem)
{
if (ShowEggSpriteAsItem && !hasItem)
return LayerOverImageEggAsItem(baseImage, species);
return LayerOverImageEggTransparentSpecies(baseImage, species);
}
var baseImage = GetBaseImage(species, form, gender, formarg, isShiny, generation);
return GetSprite(baseImage, species, heldItem, isEgg, isShiny, generation, isBoxBGRed, isAltShiny);
}
private Image LayerOverImageEggTransparentSpecies(Image baseImage, int species)
{
// Partially transparent species.
baseImage = ImageUtil.ChangeOpacity(baseImage, EggUnderLayerTransparency);
// Add the egg layer over-top with full opacity.
var egg = GetEggSprite(species);
return ImageUtil.LayerImage(baseImage, egg, 0, 0);
}
public Image GetSprite(Image baseSprite, int species, int heldItem, bool isEgg, bool isShiny, int generation = -1, bool isBoxBGRed = false, bool isAltShiny = false)
{
if (isEgg)
baseSprite = LayerOverImageEgg(baseSprite, species, heldItem != 0);
if (heldItem > 0)
baseSprite = LayerOverImageItem(baseSprite, heldItem, generation);
if (isShiny)
baseSprite = LayerOverImageShiny(baseSprite, isBoxBGRed, generation >= 8 && isAltShiny);
return baseSprite;
}
private Image LayerOverImageEggAsItem(Image baseImage, int species)
{
var egg = GetEggSprite(species);
return ImageUtil.LayerImage(baseImage, egg, EggItemShiftX, EggItemShiftY); // similar to held item, since they can't have any
}
private Image GetBaseImage(int species, int form, int gender, uint formarg, bool shiny, int generation)
{
var img = FormInfo.IsTotemForm(species, form, generation)
? GetBaseImageTotem(species, form, gender, formarg, shiny, generation)
: GetBaseImageDefault(species, form, gender, formarg, shiny, generation);
return img ?? GetBaseImageFallback(species, form, gender, formarg, shiny, generation);
}
public static void LoadSettings(ISpriteSettings sprite)
{
ShowEggSpriteAsItem = sprite.ShowEggSpriteAsHeldItem;
ShowEncounterBall = sprite.ShowEncounterBall;
private Image? GetBaseImageTotem(int species, int form, int gender, uint formarg, bool shiny, int generation)
{
var baseform = FormInfo.GetTotemBaseForm(species, form);
var baseImage = GetBaseImageDefault(species, baseform, gender, formarg, shiny, generation);
if (baseImage == null)
return null;
return ImageUtil.ToGrayscale(baseImage);
}
private Image? GetBaseImageDefault(int species, int form, int gender, uint formarg, bool shiny, int generation)
{
var file = GetSpriteAll(species, form, gender, formarg, shiny, generation);
return (Image?)Resources.ResourceManager.GetObject(file);
}
private Image GetBaseImageFallback(int species, int form, int gender, uint formarg, bool shiny, int generation)
{
if (shiny) // try again without shiny
{
var img = GetBaseImageDefault(species, form, gender, formarg, false, generation);
if (img != null)
return img;
}
// try again without form
var baseImage = (Image?)Resources.ResourceManager.GetObject(GetSpriteStringSpeciesOnly(species));
if (baseImage == null) // failed again
return Unknown;
return ImageUtil.LayerImage(baseImage, Unknown, 0, 0, UnknownFormTransparency);
}
private Image LayerOverImageItem(Image baseImage, int item, int generation)
{
Image itemimg = generation switch
{
<= 4 when item is >= 328 and <= 419 => ItemTM, // gen2/3/4 TM
8 when item is >= 328 and <= 427 => ItemTM, // BDSP TMs
>= 8 when item is >= 1130 and <= 1229 => ItemTR, // Gen8 TR
_ => (Image?)Resources.ResourceManager.GetObject(GetItemResourceName(item)) ?? UnknownItem,
};
// Redraw item in bottom right corner; since images are cropped, try to not have them at the edge
int x = baseImage.Width - itemimg.Width - ((ItemMaxSize - itemimg.Width) / 4) - ItemShiftX;
int y = baseImage.Height - itemimg.Height - ItemShiftY;
return ImageUtil.LayerImage(baseImage, itemimg, x, y);
}
private static Image LayerOverImageShiny(Image baseImage, bool isBoxBGRed, bool altShiny)
{
// Add shiny star to top left of image.
var rare = isBoxBGRed ? Resources.rare_icon_alt : Resources.rare_icon;
if (altShiny)
rare = Resources.rare_icon_2;
return ImageUtil.LayerImage(baseImage, rare, 0, 0, ShinyTransparency);
}
private Image LayerOverImageEgg(Image baseImage, int species, bool hasItem)
{
if (ShowEggSpriteAsItem && !hasItem)
return LayerOverImageEggAsItem(baseImage, species);
return LayerOverImageEggTransparentSpecies(baseImage, species);
}
private Image LayerOverImageEggTransparentSpecies(Image baseImage, int species)
{
// Partially transparent species.
baseImage = ImageUtil.ChangeOpacity(baseImage, EggUnderLayerTransparency);
// Add the egg layer over-top with full opacity.
var egg = GetEggSprite(species);
return ImageUtil.LayerImage(baseImage, egg, 0, 0);
}
private Image LayerOverImageEggAsItem(Image baseImage, int species)
{
var egg = GetEggSprite(species);
return ImageUtil.LayerImage(baseImage, egg, EggItemShiftX, EggItemShiftY); // similar to held item, since they can't have any
}
public static void LoadSettings(ISpriteSettings sprite)
{
ShowEggSpriteAsItem = sprite.ShowEggSpriteAsHeldItem;
ShowEncounterBall = sprite.ShowEncounterBall;
ShowEncounterColor = sprite.ShowEncounterColor;
ShowEncounterColorPKM = sprite.ShowEncounterColorPKM;
ShowEncounterThicknessStripe = sprite.ShowEncounterThicknessStripe;
ShowEncounterOpacityBackground = sprite.ShowEncounterOpacityBackground;
ShowEncounterOpacityStripe = sprite.ShowEncounterOpacityStripe;
ShowExperiencePercent = sprite.ShowExperiencePercent;
}
ShowEncounterColor = sprite.ShowEncounterColor;
ShowEncounterColorPKM = sprite.ShowEncounterColorPKM;
ShowEncounterThicknessStripe = sprite.ShowEncounterThicknessStripe;
ShowEncounterOpacityBackground = sprite.ShowEncounterOpacityBackground;
ShowEncounterOpacityStripe = sprite.ShowEncounterOpacityStripe;
ShowExperiencePercent = sprite.ShowExperiencePercent;
}
}

View file

@ -2,38 +2,37 @@
using PKHeX.Core;
using PKHeX.Drawing.PokeSprite.Properties;
namespace PKHeX.Drawing.PokeSprite
namespace PKHeX.Drawing.PokeSprite;
/// <summary>
/// 56 high, 68 wide sprite builder
/// </summary>
public sealed class SpriteBuilder5668 : SpriteBuilder
{
/// <summary>
/// 56 high, 68 wide sprite builder
/// </summary>
public sealed class SpriteBuilder5668 : SpriteBuilder
{
public override int Height => 56;
public override int Width => 68;
public override int Height => 56;
public override int Width => 68;
protected override int ItemShiftX => 2;
protected override int ItemShiftY => 2;
protected override int ItemMaxSize => 32;
protected override int EggItemShiftX => 18;
protected override int EggItemShiftY => 1;
protected override int ItemShiftX => 2;
protected override int ItemShiftY => 2;
protected override int ItemMaxSize => 32;
protected override int EggItemShiftX => 18;
protected override int EggItemShiftY => 1;
protected override string GetSpriteStringSpeciesOnly(int species) => 'b' + $"_{species}";
protected override string GetSpriteAll(int species, int form, int gender, uint formarg, bool shiny, int generation) => 'b' + SpriteName.GetResourceStringSprite(species, form, gender, formarg, generation, shiny);
protected override string GetItemResourceName(int item) => 'b' + $"item_{item}";
protected override Bitmap Unknown => Resources.b_unknown;
protected override Bitmap GetEggSprite(int species) => species == (int)Species.Manaphy ? Resources.b_490_e : Resources.b_egg;
protected override string GetSpriteStringSpeciesOnly(int species) => 'b' + $"_{species}";
protected override string GetSpriteAll(int species, int form, int gender, uint formarg, bool shiny, int generation) => 'b' + SpriteName.GetResourceStringSprite(species, form, gender, formarg, generation, shiny);
protected override string GetItemResourceName(int item) => 'b' + $"item_{item}";
protected override Bitmap Unknown => Resources.b_unknown;
protected override Bitmap GetEggSprite(int species) => species == (int)Species.Manaphy ? Resources.b_490_e : Resources.b_egg;
public override Bitmap Hover => Resources.slotHover68;
public override Bitmap View => Resources.slotView68;
public override Bitmap Set => Resources.slotSet68;
public override Bitmap Delete => Resources.slotDel68;
public override Bitmap Transparent => Resources.slotTrans68;
public override Bitmap Drag => Resources.slotDrag68;
public override Bitmap UnknownItem => Resources.bitem_unk;
public override Bitmap None => Resources.b_0;
public override Bitmap ItemTM => Resources.bitem_tm;
public override Bitmap ItemTR => Resources.bitem_tr;
public override Bitmap ShadowLugia => Resources.b_249x;
}
public override Bitmap Hover => Resources.slotHover68;
public override Bitmap View => Resources.slotView68;
public override Bitmap Set => Resources.slotSet68;
public override Bitmap Delete => Resources.slotDel68;
public override Bitmap Transparent => Resources.slotTrans68;
public override Bitmap Drag => Resources.slotDrag68;
public override Bitmap UnknownItem => Resources.bitem_unk;
public override Bitmap None => Resources.b_0;
public override Bitmap ItemTM => Resources.bitem_tm;
public override Bitmap ItemTR => Resources.bitem_tr;
public override Bitmap ShadowLugia => Resources.b_249x;
}

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net6.0;net46</TargetFrameworks>
<LangVersion>9</LangVersion>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

View file

@ -1,70 +1,54 @@
using PKHeX.Core;
using static PKHeX.Core.GameVersion;
namespace PKHeX.Drawing.PokeSprite
{
internal static class BoxWallpaper
{
public static bool IsWallpaperRed(GameVersion version, int wallpaperID)
{
switch (version.GetGeneration())
{
case 3:
if (CXD.Contains(version))
return wallpaperID == 7; // flame pattern in XD
namespace PKHeX.Drawing.PokeSprite;
return wallpaperID switch
{
5 => true, // Volcano
13 => E == version, // PokéCenter
_ => false,
};
case 4:
return wallpaperID switch
{
5 => true, // Volcano
12 => true, // Checks
13 => true, // PokéCenter
22 => true, // Special
_ => false,
};
case 5:
return wallpaperID switch
{
5 => true, // Volcano
12 => true, // Checks
19 => B2W2.Contains(version), // PWT
22 => B2W2.Contains(version), // Reshiram
21 => BW.Contains(version), // Zoroark
23 => BW.Contains(version), // Musical
_ => false,
};
case 6 or 7:
return wallpaperID switch
{
5 => true, // Volcano
12 => true, // PokéCenter
20 => true, // Special5 Flare/Magma
_ => false,
};
case 8 when BDSP.Contains(version):
return wallpaperID switch
{
6 => true, // Volcano
15 => true, // Checks
21 => true, // Trio
29 => true, // Nostalgic (Platinum)
30 => true, // Legend (Platinum)
_ => false,
};
case 8:
return wallpaperID switch
{
_ => true, // Bad contrast with lots of void space, better to just highlight the shiny red.
};
default:
return false;
}
}
}
internal static class BoxWallpaper
{
public static bool IsWallpaperRed(GameVersion version, int wallpaperID) => version.GetGeneration() switch
{
3 when CXD.Contains(version) => wallpaperID == 7, // flame pattern in XD
3 => wallpaperID switch
{
5 => true, // Volcano
13 => E == version, // PokéCenter
_ => false,
},
4 => wallpaperID switch
{
5 => true, // Volcano
12 => true, // Checks
13 => true, // PokéCenter
22 => true, // Special
_ => false,
},
5 => wallpaperID switch
{
5 => true, // Volcano
12 => true, // Checks
19 => B2W2.Contains(version), // PWT
22 => B2W2.Contains(version), // Reshiram
21 => BW.Contains(version), // Zoroark
23 => BW.Contains(version), // Musical
_ => false,
},
6 or 7 => wallpaperID switch
{
5 => true, // Volcano
12 => true, // PokéCenter
20 => true, // Special5 Flare/Magma
_ => false,
},
8 when BDSP.Contains(version) => wallpaperID switch
{
6 => true, // Volcano
15 => true, // Checks
21 => true, // Trio
29 => true, // Nostalgic (Platinum)
30 => true, // Legend (Platinum)
_ => false,
},
8 => true, // Bad contrast with lots of void space, better to just highlight the shiny red.
_ => false,
};
}

View file

@ -2,99 +2,98 @@
using System.Text;
using PKHeX.Core;
namespace PKHeX.Drawing.PokeSprite
namespace PKHeX.Drawing.PokeSprite;
public static class SpriteName
{
public static class SpriteName
public static bool AllowShinySprite { get; set; }
private const char Separator = '_';
private const char Cosplay = 'c';
private const char Shiny = 's';
private const char GGStarter = 'p';
/// <summary>
/// Gets the resource name of the <see cref="Ball"/> sprite.
/// </summary>
public static string GetResourceStringBall(int ball) => $"_ball{ball}";
/// <summary>
/// Gets the resource name of the Pokémon sprite.
/// </summary>
public static string GetResourceStringSprite(int species, int form, int gender, uint formarg, int generation = PKX.Generation, bool shiny = false)
{
public static bool AllowShinySprite { get; set; }
if (SpeciesDefaultFormSprite.Contains(species)) // Species who show their default sprite regardless of Form
form = 0;
private const char Separator = '_';
private const char Cosplay = 'c';
private const char Shiny = 's';
private const char GGStarter = 'p';
var sb = new StringBuilder(12); // longest expected string result
sb.Append(Separator).Append(species);
/// <summary>
/// Gets the resource name of the <see cref="Ball"/> sprite.
/// </summary>
public static string GetResourceStringBall(int ball) => $"_ball{ball}";
/// <summary>
/// Gets the resource name of the Pokémon sprite.
/// </summary>
public static string GetResourceStringSprite(int species, int form, int gender, uint formarg, int generation = PKX.Generation, bool shiny = false)
if (form != 0)
{
if (SpeciesDefaultFormSprite.Contains(species)) // Species who show their default sprite regardless of Form
form = 0;
sb.Append(Separator).Append(form);
var sb = new StringBuilder(12); // longest expected string result
sb.Append(Separator).Append(species);
if (form != 0)
if (species == (int) Species.Pikachu)
{
sb.Append(Separator).Append(form);
if (species == (int) Species.Pikachu)
if (generation == 6)
{
if (generation == 6)
{
sb.Append(Cosplay);
gender = 1; // Cosplay Pikachu gift can only be Female, but personal entries are set to be either Gender
}
else if (form == 8)
{
sb.Append(GGStarter);
}
sb.Append(Cosplay);
gender = 1; // Cosplay Pikachu gift can only be Female, but personal entries are set to be either Gender
}
else if (species == (int) Species.Eevee)
else if (form == 8)
{
if (form == 1)
sb.Append(GGStarter);
sb.Append(GGStarter);
}
}
if (gender == 1 && SpeciesGenderedSprite.Contains(species))
else if (species == (int) Species.Eevee)
{
sb.Append('f');
if (form == 1)
sb.Append(GGStarter);
}
if (species == (int) Species.Alcremie)
{
if (form == 0)
sb.Append(Separator).Append(form);
sb.Append(Separator).Append(formarg);
}
if (shiny && AllowShinySprite)
sb.Append(Shiny);
return sb.ToString();
}
if (gender == 1 && SpeciesGenderedSprite.Contains(species))
{
sb.Append('f');
}
/// <summary>
/// Species that show their default Species sprite regardless of current <see cref="PKM.Form"/>
/// </summary>
private static readonly HashSet<int> SpeciesDefaultFormSprite = new()
if (species == (int) Species.Alcremie)
{
(int)Species.Mothim,
(int)Species.Scatterbug,
(int)Species.Spewpa,
(int)Species.Rockruff,
(int)Species.Mimikyu,
(int)Species.Sinistea,
(int)Species.Polteageist,
(int)Species.Urshifu,
};
if (form == 0)
sb.Append(Separator).Append(form);
sb.Append(Separator).Append(formarg);
}
/// <summary>
/// Species that show a <see cref="PKM.Gender"/> specific Sprite
/// </summary>
private static readonly HashSet<int> SpeciesGenderedSprite = new()
{
(int)Species.Pikachu,
(int)Species.Hippopotas,
(int)Species.Hippowdon,
(int)Species.Unfezant,
(int)Species.Frillish,
(int)Species.Jellicent,
(int)Species.Pyroar,
};
if (shiny && AllowShinySprite)
sb.Append(Shiny);
return sb.ToString();
}
/// <summary>
/// Species that show their default Species sprite regardless of current <see cref="PKM.Form"/>
/// </summary>
private static readonly HashSet<int> SpeciesDefaultFormSprite = new()
{
(int)Species.Mothim,
(int)Species.Scatterbug,
(int)Species.Spewpa,
(int)Species.Rockruff,
(int)Species.Mimikyu,
(int)Species.Sinistea,
(int)Species.Polteageist,
(int)Species.Urshifu,
};
/// <summary>
/// Species that show a <see cref="PKM.Gender"/> specific Sprite
/// </summary>
private static readonly HashSet<int> SpeciesGenderedSprite = new()
{
(int)Species.Pikachu,
(int)Species.Hippopotas,
(int)Species.Hippowdon,
(int)Species.Unfezant,
(int)Species.Frillish,
(int)Species.Jellicent,
(int)Species.Pyroar,
};
}

View file

@ -4,223 +4,222 @@ using System.Linq;
using PKHeX.Core;
using PKHeX.Drawing.PokeSprite.Properties;
namespace PKHeX.Drawing.PokeSprite
namespace PKHeX.Drawing.PokeSprite;
public static class SpriteUtil
{
public static class SpriteUtil
public static readonly SpriteBuilder5668 SB8 = new();
public static SpriteBuilder Spriter { get; set; } = SB8;
private const int MaxSlotCount = 30; // slots in a box
private static int SpriteWidth => Spriter.Width;
private static int SpriteHeight => Spriter.Height;
private static int PartyMarkShiftX => SpriteWidth - 16;
private static int SlotLockShiftX => SpriteWidth - 14;
private static int SlotTeamShiftX => SpriteWidth - 19;
private static int FlagIllegalShiftY => SpriteHeight - 16;
public static void Initialize(SaveFile sav) => Spriter.Initialize(sav);
public static Image GetBallSprite(int ball)
{
public static readonly SpriteBuilder5668 SB8 = new();
public static SpriteBuilder Spriter { get; set; } = SB8;
string resource = SpriteName.GetResourceStringBall(ball);
return (Bitmap?)Resources.ResourceManager.GetObject(resource) ?? Resources._ball4; // Poké Ball (default)
}
private const int MaxSlotCount = 30; // slots in a box
private static int SpriteWidth => Spriter.Width;
private static int SpriteHeight => Spriter.Height;
private static int PartyMarkShiftX => SpriteWidth - 16;
private static int SlotLockShiftX => SpriteWidth - 14;
private static int SlotTeamShiftX => SpriteWidth - 19;
private static int FlagIllegalShiftY => SpriteHeight - 16;
public static Image? GetItemSprite(int item) => Resources.ResourceManager.GetObject($"item_{item}") as Image;
public static void Initialize(SaveFile sav) => Spriter.Initialize(sav);
public static Image GetSprite(int species, int form, int gender, uint formarg, int item, bool isegg, bool isShiny, int generation = -1, bool isBoxBGRed = false, bool isAltShiny = false)
{
return Spriter.GetSprite(species, form, gender, formarg, item, isegg, isShiny, generation, isBoxBGRed, isAltShiny);
}
public static Image GetBallSprite(int ball)
private static Image GetSprite(PKM pk, bool isBoxBGRed = false)
{
var formarg = pk is IFormArgument f ? f.FormArgument : 0;
bool alt = pk.Format >= 8 && (pk.ShinyXor == 0 || pk.FatefulEncounter || pk.Version == (int)GameVersion.GO);
var img = GetSprite(pk.Species, pk.Form, pk.Gender, formarg, pk.SpriteItem, pk.IsEgg, pk.IsShiny, pk.Format, isBoxBGRed, alt);
if (pk is IShadowPKM {IsShadow: true})
{
string resource = SpriteName.GetResourceStringBall(ball);
return (Bitmap?)Resources.ResourceManager.GetObject(resource) ?? Resources._ball4; // Poké Ball (default)
const int Lugia = (int)Species.Lugia;
if (pk.Species == Lugia) // show XD shadow sprite
img = Spriter.GetSprite(Spriter.ShadowLugia, 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);
return ImageUtil.LayerImage(glowImg, img, 0, 0);
}
if (pk is IGigantamax {CanGigantamax: true})
{
var gm = Resources.dyna;
return ImageUtil.LayerImage(img, gm, (img.Width - gm.Width) / 2, 0);
}
return img;
}
private static Image GetSprite(PKM pk, SaveFile sav, int box, int slot, bool flagIllegal = false)
{
if (!pk.Valid)
return Spriter.None;
bool inBox = (uint)slot < MaxSlotCount;
bool empty = pk.Species == 0;
var sprite = empty ? Spriter.None : pk.Sprite(isBoxBGRed: inBox && BoxWallpaper.IsWallpaperRed(sav.Version, sav.GetBoxWallpaper(box)));
if (!empty && flagIllegal)
{
var la = new LegalityAnalysis(pk, sav.Personal, box != -1 ? SlotOrigin.Box : SlotOrigin.Party);
if (!la.Valid)
sprite = ImageUtil.LayerImage(sprite, Resources.warn, 0, FlagIllegalShiftY);
else if (pk.Format >= 8 && pk.Moves.Any(Legal.GetDummiedMovesHashSet(pk).Contains))
sprite = ImageUtil.LayerImage(sprite, Resources.hint, 0, FlagIllegalShiftY);
if (SpriteBuilder.ShowEncounterColorPKM != SpriteBackgroundType.None)
sprite = ApplyEncounterColor(la.EncounterOriginal, sprite, SpriteBuilder.ShowEncounterColorPKM);
}
if (inBox) // in box
{
var flags = sav.GetSlotFlags(box, slot);
// Indicate any battle box teams & according locked state.
int team = flags.IsBattleTeam();
if (team >= 0)
sprite = ImageUtil.LayerImage(sprite, Resources.team, SlotTeamShiftX, 0);
if (flags.HasFlagFast(StorageSlotFlag.Locked))
sprite = ImageUtil.LayerImage(sprite, Resources.locked, SlotLockShiftX, 0);
// Some games store Party directly in the list of pokemon data (LGP/E). Indicate accordingly.
int party = flags.IsParty();
if (party >= 0)
sprite = ImageUtil.LayerImage(sprite, PartyMarks[party], PartyMarkShiftX, 0);
if (flags.HasFlagFast(StorageSlotFlag.Starter))
sprite = ImageUtil.LayerImage(sprite, Resources.starter, 0, 0);
}
public static Image? GetItemSprite(int item) => Resources.ResourceManager.GetObject($"item_{item}") as Image;
if (SpriteBuilder.ShowExperiencePercent)
sprite = ApplyExperience(pk, sprite);
public static Image GetSprite(int species, int form, int gender, uint formarg, int item, bool isegg, bool isShiny, int generation = -1, bool isBoxBGRed = false, bool isAltShiny = false)
return sprite;
}
public static Image ApplyEncounterColor(IEncounterTemplate enc, Image img, SpriteBackgroundType type)
{
var index = (enc.GetType().Name.GetHashCode() * 0x43FD43FD);
var color = Color.FromArgb(index);
if (type == SpriteBackgroundType.BottomStripe)
{
return Spriter.GetSprite(species, form, gender, formarg, item, isegg, isShiny, generation, isBoxBGRed, isAltShiny);
int stripeHeight = SpriteBuilder.ShowEncounterThicknessStripe; // from bottom
byte opacity = SpriteBuilder.ShowEncounterOpacityStripe;
return ImageUtil.ChangeTransparentTo(img, color, opacity, img.Width * 4 * (img.Height - stripeHeight));
}
private static Image GetSprite(PKM pk, bool isBoxBGRed = false)
else // full background
{
var formarg = pk is IFormArgument f ? f.FormArgument : 0;
bool alt = pk.Format >= 8 && (pk.ShinyXor == 0 || pk.FatefulEncounter || pk.Version == (int)GameVersion.GO);
var img = GetSprite(pk.Species, pk.Form, pk.Gender, formarg, pk.SpriteItem, pk.IsEgg, pk.IsShiny, pk.Format, isBoxBGRed, alt);
if (pk is IShadowPKM {IsShadow: true})
{
const int Lugia = (int)Species.Lugia;
if (pk.Species == Lugia) // show XD shadow sprite
img = Spriter.GetSprite(Spriter.ShadowLugia, 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);
return ImageUtil.LayerImage(glowImg, img, 0, 0);
}
if (pk is IGigantamax {CanGigantamax: true})
{
var gm = Resources.dyna;
return ImageUtil.LayerImage(img, gm, (img.Width - gm.Width) / 2, 0);
}
return img;
}
private static Image GetSprite(PKM pk, SaveFile sav, int box, int slot, bool flagIllegal = false)
{
if (!pk.Valid)
return Spriter.None;
bool inBox = (uint)slot < MaxSlotCount;
bool empty = pk.Species == 0;
var sprite = empty ? Spriter.None : pk.Sprite(isBoxBGRed: inBox && BoxWallpaper.IsWallpaperRed(sav.Version, sav.GetBoxWallpaper(box)));
if (!empty && flagIllegal)
{
var la = new LegalityAnalysis(pk, sav.Personal, box != -1 ? SlotOrigin.Box : SlotOrigin.Party);
if (!la.Valid)
sprite = ImageUtil.LayerImage(sprite, Resources.warn, 0, FlagIllegalShiftY);
else if (pk.Format >= 8 && pk.Moves.Any(Legal.GetDummiedMovesHashSet(pk).Contains))
sprite = ImageUtil.LayerImage(sprite, Resources.hint, 0, FlagIllegalShiftY);
if (SpriteBuilder.ShowEncounterColorPKM != SpriteBackgroundType.None)
sprite = ApplyEncounterColor(la.EncounterOriginal, sprite, SpriteBuilder.ShowEncounterColorPKM);
}
if (inBox) // in box
{
var flags = sav.GetSlotFlags(box, slot);
// Indicate any battle box teams & according locked state.
int team = flags.IsBattleTeam();
if (team >= 0)
sprite = ImageUtil.LayerImage(sprite, Resources.team, SlotTeamShiftX, 0);
if (flags.HasFlagFast(StorageSlotFlag.Locked))
sprite = ImageUtil.LayerImage(sprite, Resources.locked, SlotLockShiftX, 0);
// Some games store Party directly in the list of pokemon data (LGP/E). Indicate accordingly.
int party = flags.IsParty();
if (party >= 0)
sprite = ImageUtil.LayerImage(sprite, PartyMarks[party], PartyMarkShiftX, 0);
if (flags.HasFlagFast(StorageSlotFlag.Starter))
sprite = ImageUtil.LayerImage(sprite, Resources.starter, 0, 0);
}
if (SpriteBuilder.ShowExperiencePercent)
sprite = ApplyExperience(pk, sprite);
return sprite;
}
public static Image ApplyEncounterColor(IEncounterTemplate enc, Image img, SpriteBackgroundType type)
{
var index = (enc.GetType().Name.GetHashCode() * 0x43FD43FD);
var color = Color.FromArgb(index);
if (type == SpriteBackgroundType.BottomStripe)
{
int stripeHeight = SpriteBuilder.ShowEncounterThicknessStripe; // from bottom
byte opacity = SpriteBuilder.ShowEncounterOpacityStripe;
return ImageUtil.ChangeTransparentTo(img, color, opacity, img.Width * 4 * (img.Height - stripeHeight));
}
else // full background
{
byte opacity = SpriteBuilder.ShowEncounterOpacityBackground;
return ImageUtil.ChangeTransparentTo(img, color, opacity);
}
}
private static Image ApplyExperience(PKM pk, Image img)
{
const int bpp = 4;
int start = bpp * SpriteWidth * (SpriteHeight - 1);
var level = pk.CurrentLevel;
if (level == 100)
return ImageUtil.WritePixels(img, Color.Lime, start, start + (SpriteWidth * bpp));
var pct = Experience.GetEXPToLevelUpPercentage(level, pk.EXP, pk.PersonalInfo.EXPGrowth);
if (pct is 0)
return ImageUtil.WritePixels(img, Color.Yellow, start, start + (SpriteWidth * bpp));
return ImageUtil.WritePixels(img, Color.DodgerBlue, start, start + (int)(SpriteWidth * pct * bpp));
}
private static readonly Bitmap[] PartyMarks =
{
Resources.party1, Resources.party2, Resources.party3, Resources.party4, Resources.party5, Resources.party6,
};
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;
var formarg = pk is IFormArgument f ? f.FormArgument : 0;
baseSprite = GetSprite(pk.Species, pk.Form, pk.Gender, formarg, 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, 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();
ImageUtil.SetAllUsedPixelsOpaque(pixels);
ImageUtil.GlowEdges(pixels, blue, green, red, baseSprite.Width);
ImageUtil.RemovePixels(pixels, original);
}
public static Image GetLegalIndicator(bool valid) => valid ? Resources.valid : Resources.warn;
// Extension Methods
public static Image Sprite(this PKM pk, bool isBoxBGRed = false) => GetSprite(pk, isBoxBGRed);
public static Image Sprite(this IEncounterTemplate enc)
{
if (enc is MysteryGift g)
return GetMysteryGiftPreviewPoke(g);
var gender = GetDisplayGender(enc);
var img = GetSprite(enc.Species, enc.Form, gender, 0, 0, enc.EggEncounter, enc.IsShiny, enc.Generation);
if (SpriteBuilder.ShowEncounterBall && enc is IFixedBall {FixedBall: not Ball.None} b)
{
var ballSprite = GetBallSprite((int)b.FixedBall);
img = ImageUtil.LayerImage(img, ballSprite, 0, img.Height - ballSprite.Height);
}
if (enc is IGigantamax {CanGigantamax: true})
{
var gm = Resources.dyna;
img = ImageUtil.LayerImage(img, gm, (img.Width - gm.Width) / 2, 0);
}
if (SpriteBuilder.ShowEncounterColor != SpriteBackgroundType.None)
img = ApplyEncounterColor(enc, img, SpriteBuilder.ShowEncounterColor);
return img;
}
public static int GetDisplayGender(IEncounterTemplate enc) => enc switch
{
EncounterSlotGO g => (int)g.Gender & 1,
EncounterStatic s => Math.Max(0, s.Gender),
EncounterTrade t => Math.Max(0, t.Gender),
_ => 0,
};
public static Image Sprite(this PKM pk, SaveFile sav, int box, int slot, bool flagIllegal = false)
=> GetSprite(pk, sav, box, slot, flagIllegal);
public static Image GetMysteryGiftPreviewPoke(MysteryGift gift)
{
if (gift.IsEgg && gift.Species == (int)Species.Manaphy) // Manaphy Egg
return GetSprite((int)Species.Manaphy, 0, 2, 0, 0, true, false, gift.Generation);
var gender = Math.Max(0, gift.Gender);
var img = GetSprite(gift.Species, gift.Form, gender, 0, gift.HeldItem, gift.IsEgg, gift.IsShiny, gift.Generation);
if (SpriteBuilder.ShowEncounterBall && gift is IFixedBall { FixedBall: not Ball.None } b)
{
var ballSprite = GetBallSprite((int)b.FixedBall);
img = ImageUtil.LayerImage(img, ballSprite, 0, img.Height - ballSprite.Height);
}
if (gift is IGigantamax { CanGigantamax: true })
{
var gm = Resources.dyna;
img = ImageUtil.LayerImage(img, gm, (img.Width - gm.Width) / 2, 0);
}
return img;
byte opacity = SpriteBuilder.ShowEncounterOpacityBackground;
return ImageUtil.ChangeTransparentTo(img, color, opacity);
}
}
private static Image ApplyExperience(PKM pk, Image img)
{
const int bpp = 4;
int start = bpp * SpriteWidth * (SpriteHeight - 1);
var level = pk.CurrentLevel;
if (level == 100)
return ImageUtil.WritePixels(img, Color.Lime, start, start + (SpriteWidth * bpp));
var pct = Experience.GetEXPToLevelUpPercentage(level, pk.EXP, pk.PersonalInfo.EXPGrowth);
if (pct is 0)
return ImageUtil.WritePixels(img, Color.Yellow, start, start + (SpriteWidth * bpp));
return ImageUtil.WritePixels(img, Color.DodgerBlue, start, start + (int)(SpriteWidth * pct * bpp));
}
private static readonly Bitmap[] PartyMarks =
{
Resources.party1, Resources.party2, Resources.party3, Resources.party4, Resources.party5, Resources.party6,
};
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;
var formarg = pk is IFormArgument f ? f.FormArgument : 0;
baseSprite = GetSprite(pk.Species, pk.Form, pk.Gender, formarg, 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, 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();
ImageUtil.SetAllUsedPixelsOpaque(pixels);
ImageUtil.GlowEdges(pixels, blue, green, red, baseSprite.Width);
ImageUtil.RemovePixels(pixels, original);
}
public static Image GetLegalIndicator(bool valid) => valid ? Resources.valid : Resources.warn;
// Extension Methods
public static Image Sprite(this PKM pk, bool isBoxBGRed = false) => GetSprite(pk, isBoxBGRed);
public static Image Sprite(this IEncounterTemplate enc)
{
if (enc is MysteryGift g)
return GetMysteryGiftPreviewPoke(g);
var gender = GetDisplayGender(enc);
var img = GetSprite(enc.Species, enc.Form, gender, 0, 0, enc.EggEncounter, enc.IsShiny, enc.Generation);
if (SpriteBuilder.ShowEncounterBall && enc is IFixedBall {FixedBall: not Ball.None} b)
{
var ballSprite = GetBallSprite((int)b.FixedBall);
img = ImageUtil.LayerImage(img, ballSprite, 0, img.Height - ballSprite.Height);
}
if (enc is IGigantamax {CanGigantamax: true})
{
var gm = Resources.dyna;
img = ImageUtil.LayerImage(img, gm, (img.Width - gm.Width) / 2, 0);
}
if (SpriteBuilder.ShowEncounterColor != SpriteBackgroundType.None)
img = ApplyEncounterColor(enc, img, SpriteBuilder.ShowEncounterColor);
return img;
}
public static int GetDisplayGender(IEncounterTemplate enc) => enc switch
{
EncounterSlotGO g => (int)g.Gender & 1,
EncounterStatic s => Math.Max(0, s.Gender),
EncounterTrade t => Math.Max(0, t.Gender),
_ => 0,
};
public static Image Sprite(this PKM pk, SaveFile sav, int box, int slot, bool flagIllegal = false)
=> GetSprite(pk, sav, box, slot, flagIllegal);
public static Image GetMysteryGiftPreviewPoke(MysteryGift gift)
{
if (gift.IsEgg && gift.Species == (int)Species.Manaphy) // Manaphy Egg
return GetSprite((int)Species.Manaphy, 0, 2, 0, 0, true, false, gift.Generation);
var gender = Math.Max(0, gift.Gender);
var img = GetSprite(gift.Species, gift.Form, gender, 0, gift.HeldItem, gift.IsEgg, gift.IsShiny, gift.Generation);
if (SpriteBuilder.ShowEncounterBall && gift is IFixedBall { FixedBall: not Ball.None } b)
{
var ballSprite = GetBallSprite((int)b.FixedBall);
img = ImageUtil.LayerImage(img, ballSprite, 0, img.Height - ballSprite.Height);
}
if (gift is IGigantamax { CanGigantamax: true })
{
var gm = Resources.dyna;
img = ImageUtil.LayerImage(img, gm, (img.Width - gm.Width) / 2, 0);
}
return img;
}
}

View file

@ -0,0 +1,29 @@
using System;
using System.Drawing;
namespace PKHeX.Drawing;
public static class ColorUtil
{
public static Color ColorBaseStat(int v)
{
const float maxval = 180; // shift the green cap down
float x = 100f * v / maxval;
if (x > 100)
x = 100;
double red = 255f * (x > 50 ? 1 - (2 * (x - 50) / 100.0) : 1.0);
double green = 255f * (x > 50 ? 1.0 : 2 * x / 100.0);
return Blend(Color.FromArgb((int)red, (int)green, 0), Color.White, 0.4);
}
public static Color ColorBaseStatTotal(int tot) => ColorBaseStat((int) (Math.Max(0, tot - 175) / 3f));
public static Color Blend(Color color, Color backColor, double amount)
{
byte r = (byte)((color.R * amount) + (backColor.R * (1 - amount)));
byte g = (byte)((color.G * amount) + (backColor.G * (1 - amount)));
byte b = (byte)((color.B * amount) + (backColor.B * (1 - amount)));
return Color.FromArgb(r, g, b);
}
}

View file

@ -3,285 +3,262 @@ using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace PKHeX.Drawing
namespace PKHeX.Drawing;
/// <summary>
/// Image Layering/Blending Utility
/// </summary>
public static class ImageUtil
{
/// <summary>
/// Image Layering/Blending Utility
/// </summary>
public static class ImageUtil
public static Bitmap LayerImage(Image baseLayer, Image overLayer, int x, int y, double transparency)
{
public static Bitmap LayerImage(Image baseLayer, Image overLayer, int x, int y, double transparency)
overLayer = ChangeOpacity(overLayer, transparency);
return LayerImage(baseLayer, overLayer, x, y);
}
public static Bitmap LayerImage(Image baseLayer, Image overLayer, int x, int y)
{
Bitmap img = new(baseLayer);
using Graphics gr = Graphics.FromImage(img);
gr.DrawImage(overLayer, x, y, overLayer.Width, overLayer.Height);
return img;
}
public static Bitmap ChangeOpacity(Image img, double trans)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
SetAllTransparencyTo(data, trans);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static Bitmap ChangeAllColorTo(Image img, Color c)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
ChangeAllColorTo(data, c);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static Bitmap ChangeTransparentTo(Image img, Color c, byte trans, int start = 0)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
SetAllTransparencyTo(data, c, trans, start);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static Bitmap WritePixels(Image img, Color c, int start, int end)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
ChangeAllTo(data, c, start, end);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static Bitmap ToGrayscale(Image img)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
SetAllColorToGrayScale(data);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
private static void GetBitmapData(Bitmap bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data)
{
bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
ptr = bmpData.Scan0;
data = new byte[bmp.Width * bmp.Height * 4];
}
public static Bitmap GetBitmap(byte[] data, int width, int height, PixelFormat format = PixelFormat.Format32bppArgb)
{
var bmp = new Bitmap(width, height, format);
var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, format);
var ptr = bmpData.Scan0;
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static byte[] GetPixelData(Bitmap bitmap)
{
var argbData = new byte[bitmap.Width * bitmap.Height * 4];
var bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Marshal.Copy(bd.Scan0, argbData, 0, bitmap.Width * bitmap.Height * 4);
bitmap.UnlockBits(bd);
return argbData;
}
public static void SetAllUsedPixelsOpaque(byte[] data)
{
for (int i = 0; i < data.Length; i += 4)
{
overLayer = ChangeOpacity(overLayer, transparency);
return LayerImage(baseLayer, overLayer, x, y);
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);
}
public static void SetAllTransparencyTo(byte[] data, Color c, byte trans, int start)
{
byte R = c.R;
byte G = c.G;
byte B = c.B;
for (int i = start; i < data.Length; i += 4)
{
if (data[i + 3] != 0)
continue;
data[i + 0] = B;
data[i + 1] = G;
data[i + 2] = R;
data[i + 3] = trans;
}
}
public static void ChangeAllTo(byte[] data, Color c, int start, int end)
{
byte R = c.R;
byte G = c.G;
byte B = c.B;
byte A = c.A;
for (int i = start; i < end; i += 4)
{
data[i + 3] = A;
data[i + 2] = R;
data[i + 1] = G;
data[i + 0] = B;
}
}
public static void ChangeAllColorTo(byte[] data, Color c)
{
byte R = c.R;
byte G = c.G;
byte B = c.B;
for (int i = 0; i < data.Length; i += 4)
{
if (data[i + 3] == 0)
continue;
data[i + 0] = B;
data[i + 1] = G;
data[i + 2] = R;
}
}
private static void SetAllColorToGrayScale(byte[] data)
{
for (int i = 0; i < data.Length; i += 4)
{
if (data[i + 3] == 0)
continue;
byte greyS = (byte)(((0.3 * data[i + 2]) + (0.59 * data[i + 1]) + (0.11 * data[i + 0])) / 3);
data[i + 0] = greyS;
data[i + 1] = greyS;
data[i + 2] = greyS;
}
}
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)
{
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;
int x = (i % stride) / 4;
int y = (i / stride);
Pollute(x, y);
}
public static Bitmap LayerImage(Image baseLayer, Image overLayer, int x, int y)
void Pollute(int x, int y)
{
Bitmap img = new(baseLayer);
using Graphics gr = Graphics.FromImage(img);
gr.DrawImage(overLayer, x, y, overLayer.Width, overLayer.Height);
return img;
}
public static Bitmap ChangeOpacity(Image img, double trans)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
SetAllTransparencyTo(data, trans);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static Bitmap ChangeAllColorTo(Image img, Color c)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
ChangeAllColorTo(data, c);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static Bitmap ChangeTransparentTo(Image img, Color c, byte trans, int start = 0)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
SetAllTransparencyTo(data, c, trans, start);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static Bitmap WritePixels(Image img, Color c, int start, int end)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
ChangeAllTo(data, c, start, end);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static Bitmap ToGrayscale(Image img)
{
var bmp = (Bitmap)img.Clone();
GetBitmapData(bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data);
Marshal.Copy(ptr, data, 0, data.Length);
SetAllColorToGrayScale(data);
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
private static void GetBitmapData(Bitmap bmp, out BitmapData bmpData, out IntPtr ptr, out byte[] data)
{
bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
ptr = bmpData.Scan0;
data = new byte[bmp.Width * bmp.Height * 4];
}
public static Bitmap GetBitmap(byte[] data, int width, int height, PixelFormat format = PixelFormat.Format32bppArgb)
{
var bmp = new Bitmap(width, height, format);
var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, format);
var ptr = bmpData.Scan0;
Marshal.Copy(data, 0, ptr, data.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
public static byte[] GetPixelData(Bitmap bitmap)
{
var argbData = new byte[bitmap.Width * bitmap.Height * 4];
var bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Marshal.Copy(bd.Scan0, argbData, 0, bitmap.Width * bitmap.Height * 4);
bitmap.UnlockBits(bd);
return argbData;
}
public static void SetAllUsedPixelsOpaque(byte[] data)
{
for (int i = 0; i < data.Length; i += 4)
int left = Math.Max(0, x - reach);
int right = Math.Min(width - 1, x + reach);
int top = Math.Max(0, y - reach);
int bottom = Math.Min(height - 1, y + reach);
for (int i = left; i <= right; i++)
{
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);
}
public static void SetAllTransparencyTo(byte[] data, Color c, byte trans, int start)
{
byte R = c.R;
byte G = c.G;
byte B = c.B;
for (int i = start; i < data.Length; i += 4)
{
if (data[i + 3] != 0)
continue;
data[i + 0] = B;
data[i + 1] = G;
data[i + 2] = R;
data[i + 3] = trans;
}
}
public static void ChangeAllTo(byte[] data, Color c, int start, int end)
{
byte R = c.R;
byte G = c.G;
byte B = c.B;
byte A = c.A;
for (int i = start; i < end; i += 4)
{
data[i + 3] = A;
data[i + 2] = R;
data[i + 1] = G;
data[i + 0] = B;
}
}
public static void ChangeAllColorTo(byte[] data, Color c)
{
byte R = c.R;
byte G = c.G;
byte B = c.B;
for (int i = 0; i < data.Length; i += 4)
{
if (data[i + 3] == 0)
continue;
data[i + 0] = B;
data[i + 1] = G;
data[i + 2] = R;
}
}
private static void SetAllColorToGrayScale(byte[] data)
{
for (int i = 0; i < data.Length; i += 4)
{
if (data[i + 3] == 0)
continue;
byte greyS = (byte)(((0.3 * data[i + 2]) + (0.59 * data[i + 1]) + (0.11 * data[i + 0])) / 3);
data[i + 0] = greyS;
data[i + 1] = greyS;
data[i + 2] = greyS;
}
}
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)
{
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;
int x = (i % stride) / 4;
int y = (i / stride);
Pollute(x, y);
}
void Pollute(int x, int y)
{
int left = Math.Max(0, x - reach);
int right = Math.Min(width - 1, x + reach);
int top = Math.Max(0, y - reach);
int bottom = Math.Min(height - 1, y + reach);
for (int i = left; i <= right; i++)
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));
data[c + 0] += (byte)(amount * (0xFF - data[c + 0]));
}
// 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)
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)
continue;
// only clean if the current pixel isn't transparent
if (data[i + 3] != 0)
continue;
// grab the transparency from the donor byte
var transparency = data[i + 0];
if (transparency == 0)
continue;
// grab the transparency from the donor byte
var transparency = data[i + 0];
if (transparency == 0)
continue;
data[i + 0] = blue;
data[i + 1] = green;
data[i + 2] = red;
data[i + 3] = transparency;
}
}
public static Color ColorBaseStat(int v)
{
const float maxval = 180; // shift the green cap down
float x = 100f * v / maxval;
if (x > 100)
x = 100;
double red = 255f * (x > 50 ? 1 - (2 * (x - 50) / 100.0) : 1.0);
double green = 255f * (x > 50 ? 1.0 : 2 * x / 100.0);
return Blend(Color.FromArgb((int)red, (int)green, 0), Color.White, 0.4);
}
public static Color ColorBaseStatTotal(int tot) => ColorBaseStat((int) (Math.Max(0, tot - 175) / 3f));
public static Color Blend(Color color, Color backColor, double amount)
{
byte r = (byte)((color.R * amount) + (backColor.R * (1 - amount)));
byte g = (byte)((color.G * amount) + (backColor.G * (1 - amount)));
byte b = (byte)((color.B * amount) + (backColor.B * (1 - amount)));
return Color.FromArgb(r, g, b);
data[i + 0] = blue;
data[i + 1] = green;
data[i + 2] = red;
data[i + 3] = transparency;
}
}
}

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net6.0;net46</TargetFrameworks>
<LangVersion>9</LangVersion>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>

View file

@ -368,11 +368,11 @@ namespace PKHeX.WinForms.Controls
for (int i = 0; i < stats.Count; i++)
{
MT_Base[i].Text = stats[i].ToString("000");
MT_Base[i].BackColor = ImageUtil.ColorBaseStat(stats[i]);
MT_Base[i].BackColor = ColorUtil.ColorBaseStat(stats[i]);
}
var bst = pi.Stats.Sum();
TB_BST.Text = bst.ToString("000");
TB_BST.BackColor = ImageUtil.ColorBaseStatTotal(bst);
TB_BST.BackColor = ColorUtil.ColorBaseStatTotal(bst);
}
public void UpdateRandomIVs(object sender, EventArgs e)

View file

@ -114,6 +114,6 @@ namespace PKHeX.WinForms.Controls
return cache[frameIndex] = frame;
}
private Color GetFrameColor(double elapsedFraction) => ImageUtil.Blend(GlowToColor, GlowFromColor, elapsedFraction);
private Color GetFrameColor(double elapsedFraction) => ColorUtil.Blend(GlowToColor, GlowFromColor, elapsedFraction);
}
}
}

View file

@ -55,22 +55,22 @@ namespace PKHeX.WinForms
row.Cells[r++].Value = SpriteUtil.GetSprite(s, f, 0, 0, 0, false, false, SAV.Generation);
row.Cells[r++].Value = species[index];
row.Cells[r++].Value = GetIsNative(p, s);
row.Cells[r].Style.BackColor = ImageUtil.ColorBaseStatTotal(p.BST);
row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStatTotal(p.BST);
row.Cells[r++].Value = p.BST.ToString("000");
row.Cells[r++].Value = p.CatchRate.ToString("000");
row.Cells[r++].Value = TypeSpriteUtil.GetTypeSprite(p.Type1, SAV.Generation);
row.Cells[r++].Value = p.Type1 == p.Type2 ? SpriteUtil.Spriter.Transparent : TypeSpriteUtil.GetTypeSprite(p.Type2, SAV.Generation);
row.Cells[r].Style.BackColor = ImageUtil.ColorBaseStat(p.HP);
row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.HP);
row.Cells[r++].Value = p.HP.ToString("000");
row.Cells[r].Style.BackColor = ImageUtil.ColorBaseStat(p.ATK);
row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.ATK);
row.Cells[r++].Value = p.ATK.ToString("000");
row.Cells[r].Style.BackColor = ImageUtil.ColorBaseStat(p.DEF);
row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.DEF);
row.Cells[r++].Value = p.DEF.ToString("000");
row.Cells[r].Style.BackColor = ImageUtil.ColorBaseStat(p.SPA);
row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.SPA);
row.Cells[r++].Value = p.SPA.ToString("000");
row.Cells[r].Style.BackColor = ImageUtil.ColorBaseStat(p.SPD);
row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.SPD);
row.Cells[r++].Value = p.SPD.ToString("000");
row.Cells[r].Style.BackColor = ImageUtil.ColorBaseStat(p.SPE);
row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.SPE);
row.Cells[r++].Value = p.SPE.ToString("000");
var abils = p.Abilities;
row.Cells[r++].Value = GetAbility(abils, 0);