From 37c284eff36aa669c53b837d03d33b830003973b Mon Sep 17 00:00:00 2001 From: MayNL Date: Sat, 1 Aug 2026 20:11:27 -0400 Subject: [PATCH] Fixed several skin paper doll related issues -Fixed cancelled MessageBox erroneously stating "Node not extracted" while using the Export Skin Icon tool. -Skin icons can no longer be exported for skins with abnormal resolutions. -Skin paper dolls can no longer be created for skins with abnormal resolutions. A default skin icon will be returned instead. -Fix crash for skin textures smaller than the base resolution skin size -Fix crash for boxes placed outside of the base resolution skin canvas --- PCK-Studio/Controls/Editor/PckEditor.cs | 23 +++++++--- PckStudio.Core/Extensions/SkinExtensions.cs | 49 +++++++++++++++++++-- PckStudio.Core/Skin/SkinBOX.cs | 2 +- 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/PCK-Studio/Controls/Editor/PckEditor.cs b/PCK-Studio/Controls/Editor/PckEditor.cs index 3c71c15d..b07905df 100644 --- a/PCK-Studio/Controls/Editor/PckEditor.cs +++ b/PCK-Studio/Controls/Editor/PckEditor.cs @@ -839,6 +839,12 @@ namespace PckStudio.Controls Bitmap customIcon = new Bitmap(skinIconWidth, skinIconHeight); + // 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); + if (croppedPaperDoll is null) + return GetDefaultSkinNodeIconKey(skin.Anim); + using (Graphics gfx = Graphics.FromImage(customIcon)) { gfx.InterpolationMode = InterpolationMode.NearestNeighbor; @@ -847,10 +853,6 @@ namespace PckStudio.Controls 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); - gfx.DrawImage(croppedPaperDoll, 0, 0); // the crop bust is already the same dimensions as the custom icon gfx.DrawImage(Resources.CUSTOM_SKIN_ICON, 0, 0); // draw border on top @@ -2523,7 +2525,7 @@ namespace PckStudio.Controls // Makes sure chosen directory isn't null or whitespace AKA makes sure its usable string.IsNullOrWhiteSpace(Path.GetDirectoryName(saveFileDialog.FileName))) { - MessageBox.Show(this, "The chosen directory is invalid. Please choose a different one and try again.", "Node not extracted"); + MessageBox.Show(this, "The chosen directory is invalid. Please choose a different one and try again.", "Icon not exported"); return; } @@ -2541,7 +2543,16 @@ namespace PckStudio.Controls return; } - skin.DrawPaperDoll(bustCrop: cropped).Save(saveFileDialog.FileName); + Image finalDoll = skin.DrawPaperDoll(bustCrop: cropped); + + if (finalDoll is null) + { + MessageBox.Show(this, "Cannot create an icon from an invalid Minecraft skin.", "Icon not exported"); + + return; + } + + finalDoll.Save(saveFileDialog.FileName); } } diff --git a/PckStudio.Core/Extensions/SkinExtensions.cs b/PckStudio.Core/Extensions/SkinExtensions.cs index db548e5e..928b7880 100644 --- a/PckStudio.Core/Extensions/SkinExtensions.cs +++ b/PckStudio.Core/Extensions/SkinExtensions.cs @@ -74,14 +74,43 @@ namespace PckStudio.Core.Extensions { //pixel scale set to 10 so inflated parts can be seen as properly as possible + Image skinTexture = skin.Texture; + if (skinTexture.Width != skinTexture.Height && skinTexture.Width / skinTexture.Height != 2) + { + return null; + } + bool isWideSkin = skin.Anim.GetFlag(SkinAnimFlag.MODERN_WIDE_MODEL); bool isSlimSkin = skin.Anim.GetFlag(SkinAnimFlag.SLIM_MODEL); bool isModernSkin = isWideSkin || isSlimSkin; bool isDinnerbone = skin.Anim.GetFlag(SkinAnimFlag.DINNERBONE); bool isStatueOfLiberty = skin.Anim.GetFlag(SkinAnimFlag.STATUE_OF_LIBERTY); - float textureScaleX = skin.Texture.Width / 64f; // minecraft skins always have a width of 64 - float textureScaleY = skin.Texture.Height / (isModernSkin ? 64f : 32f); + int skinHeight = isModernSkin ? 64 : 32; + + // fix for abnormal skin textures that are smaller than 64 + if (skinTexture.Width != 64 || skinTexture.Height != skinHeight) + { + Bitmap resized = new Bitmap(64, skinHeight); + + using (Graphics gfx = Graphics.FromImage(resized)) + { + gfx.InterpolationMode = InterpolationMode.NearestNeighbor; + gfx.PixelOffsetMode = PixelOffsetMode.Half; + gfx.SmoothingMode = SmoothingMode.None; + gfx.CompositingMode = CompositingMode.SourceCopy; + + gfx.DrawImage( + skinTexture, + new Rectangle(0, 0, 64, skinHeight) + ); + } + + skinTexture = resized; + } + + float textureScaleX = skinTexture.Width / 64f; // minecraft skins always have a width of 64 + float textureScaleY = skinTexture.Height / (isModernSkin ? 64f : 32f); // start with a large canvas and crop down later Image paperDoll = new Bitmap(512, 512); @@ -220,6 +249,8 @@ namespace PckStudio.Core.Extensions bool isStatueOfLibertyArm = isStatueOfLiberty && (box.Type == "ARM0" || box.Type == "SLEEVE0" || box.Type == "ARMARMOR0"); + box.UV = new (box.UV.X % 64, box.UV.Y % (isModernSkin ? 64f : 32f)); + // this math is basically to ensure the face is stretched if the texture is improper Rectangle faceRect = new Rectangle( isStatueOfLibertyArm // get back of ARM 0 box if Statue of Liberty @@ -230,6 +261,11 @@ namespace PckStudio.Core.Extensions (int)(boxHeight * textureScaleY) ); + if(faceRect.Size.Height == 0) + { + faceRect.Size = new Size(faceRect.X, 1); + } + // get back of the face to display behind just incase transparent parts exist on the front layer Rectangle backFaceRect = new Rectangle( !isStatueOfLibertyArm // get front of ARM 0 box if Statue of Liberty @@ -240,8 +276,13 @@ namespace PckStudio.Core.Extensions (int)(boxHeight * textureScaleY) ); - Image boxFace = skin.Texture.GetArea(faceRect); - Image backBoxFace = skin.Texture.GetArea(backFaceRect); + if (backFaceRect.Size.Height == 0) + { + backFaceRect.Size = new Size(backFaceRect.X, 1); + } + + Image boxFace = skinTexture.GetArea(faceRect); + Image backBoxFace = skinTexture.GetArea(backFaceRect); if (box.Mirror) { diff --git a/PckStudio.Core/Skin/SkinBOX.cs b/PckStudio.Core/Skin/SkinBOX.cs index 5b33aba8..3fba659b 100644 --- a/PckStudio.Core/Skin/SkinBOX.cs +++ b/PckStudio.Core/Skin/SkinBOX.cs @@ -89,7 +89,7 @@ namespace PckStudio.Core.Skin public string Type { get; } public Vector3 Pos { get; } public Vector3 Size { get; } - public Vector2 UV { get; } + public Vector2 UV { get; set; } // changing this to simulate texture wrapping for paper doll drawing. this might be a temporary change -May public SkinArmorFlags ArmorMaskFlags { get; } public bool Mirror { get; } public float Scale { get; }