mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/PCK-Studio.git
synced 2026-07-06 17:28:07 +00:00
Added custom icons for skins and capes
-Added settings for custom icons -Added custom icons
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using DiscordRPC;
|
||||
using MetroFramework.Forms;
|
||||
using MetroFramework.Forms;
|
||||
using OMI.Formats.Behaviour;
|
||||
using OMI.Formats.Color;
|
||||
using OMI.Formats.GameRule;
|
||||
@@ -15,6 +14,7 @@ using OMI.Workers.Language;
|
||||
using OMI.Workers.Material;
|
||||
using OMI.Workers.Model;
|
||||
using OMI.Workers.Pck;
|
||||
using OpenTK;
|
||||
using PckStudio.Core;
|
||||
using PckStudio.Core.Deserializer;
|
||||
using PckStudio.Core.Extensions;
|
||||
@@ -43,7 +43,7 @@ using System.Drawing.Drawing2D;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Forms.DataVisualization.Charting;
|
||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
|
||||
|
||||
namespace PckStudio.Controls
|
||||
{
|
||||
@@ -83,6 +83,30 @@ namespace PckStudio.Controls
|
||||
LittleEndianCheckBox.Visible = packInfo.AllowEndianSwap;
|
||||
|
||||
treeViewMain.TreeViewNodeSorter = new PckNodeSorter();
|
||||
treeViewMain.DrawMode = TreeViewDrawMode.OwnerDrawText;
|
||||
treeViewMain.ImageList = new ImageList();
|
||||
treeViewMain.ImageList.Images.Add(Resources.empty); // no image drawn to allow for custom drawing
|
||||
int iconSize = 48;
|
||||
treeViewMain.ImageList.ImageSize = new Size(iconSize, iconSize);
|
||||
treeViewMain.DrawNode += (s, e) =>
|
||||
{
|
||||
e.DrawDefault = true;
|
||||
|
||||
Rectangle iconBounds = new Rectangle(
|
||||
e.Bounds.Left - iconSize - 2,
|
||||
e.Bounds.Top,
|
||||
iconSize,
|
||||
iconSize
|
||||
);
|
||||
|
||||
Image customIcon = GetNodeIcon(e.Node.Tag is PckAsset asset ? asset : null);
|
||||
|
||||
if (customIcon != null)
|
||||
{
|
||||
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; // bicubic to make it look better when downscaled
|
||||
e.Graphics.DrawImage(customIcon, iconBounds);
|
||||
}
|
||||
};
|
||||
|
||||
skinToolStripMenuItem1.Click += (sender, e) => SetFileType(PckAssetType.SkinFile);
|
||||
capeToolStripMenuItem.Click += (sender, e) => SetFileType(PckAssetType.CapeFile);
|
||||
@@ -136,6 +160,29 @@ namespace PckStudio.Controls
|
||||
};
|
||||
}
|
||||
|
||||
private void TreeViewMain_DrawNode(object sender, DrawTreeNodeEventArgs e)
|
||||
{
|
||||
// If not selected, draw normally
|
||||
if ((e.State & TreeNodeStates.Selected) == 0)
|
||||
{
|
||||
e.DrawDefault = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw background
|
||||
e.Graphics.FillRectangle(SystemBrushes.Highlight, e.Bounds);
|
||||
|
||||
// Draw text
|
||||
TextRenderer.DrawText(
|
||||
e.Graphics,
|
||||
e.Node.Text,
|
||||
treeViewMain.Font,
|
||||
e.Bounds,
|
||||
SystemColors.HighlightText,
|
||||
TextFormatFlags.GlyphOverhangPadding
|
||||
);
|
||||
}
|
||||
|
||||
protected override void PreSave()
|
||||
{
|
||||
EditorValue.Endianness = LittleEndianCheckBox.Checked ? OMI.ByteOrder.LittleEndian : OMI.ByteOrder.BigEndian;
|
||||
@@ -162,7 +209,7 @@ namespace PckStudio.Controls
|
||||
|
||||
public override void UpdateView()
|
||||
{
|
||||
BuildMainTreeView();
|
||||
BuildMainTreeView(true);
|
||||
}
|
||||
|
||||
private void HandleInnerPckFile(PckAsset asset)
|
||||
@@ -578,24 +625,24 @@ namespace PckStudio.Controls
|
||||
return BuildNodeTreeBySeperator(subNode.Nodes, subPath, seperator);
|
||||
}
|
||||
|
||||
private void BuildPckTreeView(TreeNodeCollection root, PckFile pckFile)
|
||||
private void BuildPckTreeView(TreeNodeCollection root, PckFile pckFile, bool resetNodes = false)
|
||||
{
|
||||
foreach (PckAsset asset in pckFile.GetAssets())
|
||||
{
|
||||
TreeNode node = BuildNodeTreeBySeperator(root, asset.Filename, '/');
|
||||
node.Tag = asset;
|
||||
node.ImageKey = node.SelectedImageKey = GetNodeIconKey(asset);
|
||||
node.ImageKey = node.SelectedImageKey = GetNodeIconKey(asset, resetNodes);
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildMainTreeView()
|
||||
private void BuildMainTreeView(bool resetNodes = false)
|
||||
{
|
||||
// In case the Rename function was just used and the selected node name no longer matches the file name
|
||||
string selectedNodeText = treeViewMain.SelectedNode is TreeNode node ? node.FullPath : string.Empty;
|
||||
previewPictureBox.Image = Resources.NoImageFound;
|
||||
treeParameters.Nodes.Clear();
|
||||
treeViewMain.Nodes.Clear();
|
||||
BuildPckTreeView(treeViewMain.Nodes, EditorValue.File);
|
||||
BuildPckTreeView(treeViewMain.Nodes, EditorValue.File, resetNodes);
|
||||
treeViewMain.Sort();
|
||||
|
||||
TreeNode[] selectedNodes = treeViewMain.FindPath(selectedNodeText);
|
||||
@@ -605,29 +652,165 @@ namespace PckStudio.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private int GetSkinNodeIconId(int animValue)
|
||||
private string GetDefaultSkinNodeIconKey(SkinANIM anim)
|
||||
{
|
||||
SkinANIM anim = SkinANIM.FromValue(animValue);
|
||||
|
||||
if (anim.GetFlag(SkinAnimFlag.SLIM_MODEL))
|
||||
return 18; // slim model icon
|
||||
return "slimSkinFileIcon";
|
||||
else if (anim.GetFlag(SkinAnimFlag.MODERN_WIDE_MODEL))
|
||||
return 17; // modern model; slim gets priority just like in game
|
||||
return "modernSkinFileIcon"; // slim gets priority just like in game
|
||||
|
||||
return 12; // classic skin model icon
|
||||
return "classicSkinFileIcon"; // classic skin model icon
|
||||
}
|
||||
|
||||
private string GetNodeIconKey(PckAsset asset)
|
||||
private string GetCapeNodeIconKey(PckAsset cape, bool reset)
|
||||
{
|
||||
//int anim = 0;
|
||||
|
||||
//if (asset.Type == PckAssetType.SkinFile)
|
||||
// anim = asset.GetSkin().Anim.ToValue();
|
||||
Image capeTexture = null;
|
||||
|
||||
try
|
||||
{
|
||||
capeTexture = cape.GetTexture();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "capeFileIcon"; // fallback if invalid skin file
|
||||
}
|
||||
|
||||
string id = Path.GetFileNameWithoutExtension(cape.Filename);
|
||||
|
||||
if (imageList.Images.ContainsKey(id) && !reset)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
else
|
||||
{
|
||||
Bitmap customIcon = new Bitmap(Resources.CUSTOM_SKIN_ICON.Width, Resources.CUSTOM_SKIN_ICON.Height);
|
||||
|
||||
float textureScaleX = capeTexture.Width / 64f; // minecraft capes always have a width of 64
|
||||
float textureScaleY = capeTexture.Height / 32f; // minecraft capes always have a height of 32
|
||||
|
||||
float width = 10;
|
||||
float height = 16;
|
||||
float depth = 1;
|
||||
|
||||
// this math is basically to ensure the face is stretched if the texture is improper
|
||||
Rectangle faceRect = new Rectangle(
|
||||
(int)(depth * textureScaleX + depth * textureScaleX),
|
||||
(int)(depth * textureScaleY + depth * textureScaleY),
|
||||
(int)(width * textureScaleX),
|
||||
(int)(height * textureScaleY)
|
||||
);
|
||||
|
||||
Image capeFace = capeTexture.GetArea(faceRect);
|
||||
|
||||
using (Graphics gfx = Graphics.FromImage(customIcon))
|
||||
{
|
||||
gfx.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||||
gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
gfx.SmoothingMode = SmoothingMode.None;
|
||||
|
||||
gfx.Fill(new Rectangle(0, 0, customIcon.Width, customIcon.Height), Color.FromArgb(0xFF, 0x5D, 0x9C, 0xEC));
|
||||
|
||||
gfx.DrawImage(capeFace, 56, 12, 145, 232); // position for the cape on the icon
|
||||
|
||||
gfx.DrawImage(Resources.CUSTOM_SKIN_ICON, 0, 0);
|
||||
}
|
||||
|
||||
// replace icon by removing and importing at index
|
||||
int index = imageList.Images.IndexOfKey(id);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
imageList.Images.RemoveByKey(id);
|
||||
imageList.Images.Insert(index, id, customIcon);
|
||||
}
|
||||
else
|
||||
imageList.Images.Add(id, customIcon);
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSkinNodeIconKey(Skin skin, bool reset)
|
||||
{
|
||||
if (skin == null)
|
||||
return GetDefaultSkinNodeIconKey(SkinANIM.Empty);
|
||||
|
||||
string id = "dlcskin" + skin.Identifier.ToString();
|
||||
|
||||
if (imageList.Images.ContainsKey(id) && !reset)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
else
|
||||
{
|
||||
int skinIconWidth = Resources.SKINS_ICON.Width;
|
||||
int skinIconHeight = Resources.SKINS_ICON.Height;
|
||||
|
||||
Bitmap customIcon = new Bitmap(skinIconWidth, skinIconHeight);
|
||||
|
||||
using (Graphics gfx = Graphics.FromImage(customIcon))
|
||||
{
|
||||
gfx.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||||
gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
gfx.SmoothingMode = SmoothingMode.None;
|
||||
|
||||
gfx.Fill(new Rectangle(0, 0, skinIconWidth, skinIconHeight), Color.FromArgb(0xFF, 0x5D, 0x9C, 0xEC));
|
||||
|
||||
// get the bust crop for the image
|
||||
// this is handled externally to use the skin data to ensure skins are positioned in context of the default player head
|
||||
Image croppedPaperDoll = skin.DrawPaperDoll(xmlVersion: EditorValue.File.xmlVersion, bustCrop: true);
|
||||
|
||||
skin.DrawPaperDoll(xmlVersion: EditorValue.File.xmlVersion).Save("C:\\Users\\MattN\\Pictures\\icontest\\" + skin.Identifier.Id.ToString() + ".png");
|
||||
|
||||
gfx.DrawImage(croppedPaperDoll, 0, 0); // the crop bust is already the same dimensions as the skin icon
|
||||
|
||||
gfx.DrawImage(Resources.CUSTOM_SKIN_ICON, 0, 0); // draw border on top
|
||||
|
||||
customIcon.Save("C:\\Users\\MattN\\Pictures\\icontest\\" + skin.Identifier.Id.ToString() + "i.png");
|
||||
}
|
||||
|
||||
// replace icon by removing and importing at index
|
||||
int index = imageList.Images.IndexOfKey(id);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
imageList.Images.RemoveByKey(id);
|
||||
imageList.Images.Insert(index, id, customIcon);
|
||||
}
|
||||
else
|
||||
imageList.Images.Add(id, customIcon);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private Image GetNodeIcon(PckAsset asset = null)
|
||||
{
|
||||
if (asset == null)
|
||||
return Resources.ZZFolder;
|
||||
|
||||
return imageList.Images[GetNodeIconKey(asset)];
|
||||
}
|
||||
|
||||
private string GetNodeIconKey(PckAsset asset, bool reset = false)
|
||||
{
|
||||
Skin skin = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (asset.Type == PckAssetType.SkinFile)
|
||||
skin = asset.GetSkin();
|
||||
}
|
||||
catch
|
||||
{
|
||||
skin = null;
|
||||
}
|
||||
|
||||
return asset.Type switch
|
||||
{
|
||||
PckAssetType.SkinFile => "classicSkinFileIcon", //GetSkinNodeIconId(anim),
|
||||
PckAssetType.CapeFile => "capeFileIcon",
|
||||
PckAssetType.SkinFile => GetSkinNodeIconKey(skin, reset),
|
||||
PckAssetType.CapeFile => GetCapeNodeIconKey(asset, reset),
|
||||
PckAssetType.TextureFile => "textureFileIcon",
|
||||
PckAssetType.InfoFile => "infoFileIcon",
|
||||
PckAssetType.TexturePackInfoFile => "pckFileIcon",
|
||||
@@ -892,7 +1075,7 @@ namespace PckStudio.Controls
|
||||
Debug.WriteLine($"Setting {asset.Type} to {type}");
|
||||
asset.Type = type;
|
||||
TreeNode node = treeViewMain.SelectedNode;
|
||||
node.ImageKey = node.SelectedImageKey = GetNodeIconKey(asset);
|
||||
node.ImageKey = node.SelectedImageKey = GetNodeIconKey(asset, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1791,6 +1974,12 @@ namespace PckStudio.Controls
|
||||
asset.SetData(File.ReadAllBytes(ofd.FileName));
|
||||
asset.Filename = asset.Filename.Replace(fileExt, newFileExt);
|
||||
_wasModified = true;
|
||||
|
||||
if(asset.Type == PckAssetType.SkinFile || asset.Type == PckAssetType.CapeFile)
|
||||
{
|
||||
GetNodeIconKey(asset, true); // reset node icon
|
||||
}
|
||||
|
||||
BuildMainTreeView();
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -912,7 +912,7 @@
|
||||
<value>True</value>
|
||||
</data>
|
||||
<data name="metroLabel2.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>204, 108</value>
|
||||
<value>76, 81</value>
|
||||
</data>
|
||||
<data name="metroLabel2.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>0, 0</value>
|
||||
@@ -957,7 +957,7 @@
|
||||
<value>False</value>
|
||||
</data>
|
||||
<data name="ParameterTypeTextBox.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>215, 78</value>
|
||||
<value>215, 51</value>
|
||||
</data>
|
||||
<data name="ParameterTypeTextBox.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>146, 20</value>
|
||||
@@ -1002,7 +1002,7 @@
|
||||
<value>False</value>
|
||||
</data>
|
||||
<data name="parameterValueTextBox.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>215, 110</value>
|
||||
<value>215, 83</value>
|
||||
</data>
|
||||
<data name="parameterValueTextBox.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>146, 20</value>
|
||||
@@ -1026,7 +1026,7 @@
|
||||
<value>Bottom, Right</value>
|
||||
</data>
|
||||
<data name="buttonEdit.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>215, 136</value>
|
||||
<value>215, 109</value>
|
||||
</data>
|
||||
<data name="buttonEdit.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>146, 33</value>
|
||||
@@ -1059,7 +1059,7 @@
|
||||
<value>True</value>
|
||||
</data>
|
||||
<data name="metroLabel1.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>266, 1</value>
|
||||
<value>138, -26</value>
|
||||
</data>
|
||||
<data name="metroLabel1.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>0, 0</value>
|
||||
@@ -1133,7 +1133,7 @@
|
||||
<value>Edit All Parameters</value>
|
||||
</data>
|
||||
<data name="contextMenuParameterTree.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>206, 114</value>
|
||||
<value>206, 92</value>
|
||||
</data>
|
||||
<data name=">>contextMenuParameterTree.Name" xml:space="preserve">
|
||||
<value>contextMenuParameterTree</value>
|
||||
@@ -1172,7 +1172,7 @@
|
||||
<value>5, 5, 5, 5</value>
|
||||
</data>
|
||||
<data name="MetaTab.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>724, 239</value>
|
||||
<value>596, 239</value>
|
||||
</data>
|
||||
<data name="MetaTab.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>0</value>
|
||||
@@ -1196,10 +1196,10 @@
|
||||
<value>Bottom</value>
|
||||
</data>
|
||||
<data name="ParametersTabControl.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>279, 270</value>
|
||||
<value>407, 270</value>
|
||||
</data>
|
||||
<data name="ParametersTabControl.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>732, 281</value>
|
||||
<value>604, 281</value>
|
||||
</data>
|
||||
<data name="ParametersTabControl.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>11</value>
|
||||
@@ -1679,7 +1679,7 @@
|
||||
<value>204, 20</value>
|
||||
</metadata>
|
||||
<data name="imageList.ImageSize" type="System.Drawing.Size, System.Drawing">
|
||||
<value>32, 32</value>
|
||||
<value>64, 64</value>
|
||||
</data>
|
||||
<data name="treeViewMain.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>5, 50</value>
|
||||
@@ -1688,7 +1688,7 @@
|
||||
<value>0</value>
|
||||
</data>
|
||||
<data name="treeViewMain.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>274, 501</value>
|
||||
<value>402, 501</value>
|
||||
</data>
|
||||
<data name="treeViewMain.TabIndex" type="System.Int32, mscorlib">
|
||||
<value>20</value>
|
||||
@@ -1759,7 +1759,7 @@
|
||||
<value>NoControl</value>
|
||||
</data>
|
||||
<data name="previewPictureBox.Location" type="System.Drawing.Point, System.Drawing">
|
||||
<value>285, 50</value>
|
||||
<value>413, 56</value>
|
||||
</data>
|
||||
<data name="previewPictureBox.Size" type="System.Drawing.Size, System.Drawing">
|
||||
<value>218, 218</value>
|
||||
|
||||
@@ -177,8 +177,6 @@ namespace PckStudio.Core.Extensions
|
||||
.OrderBy(box => box.Scale)
|
||||
.ToList();
|
||||
|
||||
// Minecraft skins are ordinarily 16 pixels wide and 32 pixels tall
|
||||
// a padding of 4 has been added for fitting overlay parts
|
||||
float canvasWidth = 0;
|
||||
float canvasHeight = 0;
|
||||
float canvasMinX = float.MaxValue;
|
||||
|
||||
Reference in New Issue
Block a user