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
This commit is contained in:
MayNL
2026-08-01 20:11:27 -04:00
parent 5089e40990
commit 37c284eff3
3 changed files with 63 additions and 11 deletions

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -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; }