From fbc0f66aeaa4e0e94b211ec0b66fa8d8fee9d44c Mon Sep 17 00:00:00 2001
From: miku-666 <74728189+NessieHax@users.noreply.github.com>
Date: Sat, 29 Nov 2025 11:05:32 +0100
Subject: [PATCH] PckStudio - Add SkinsPanel.cs
---
PCK-Studio/Controls/PckAssetBrowserEditor.cs | 4 +
PCK-Studio/Controls/SkinsPanel.Designer.cs | 119 +++++++++++++++++
PCK-Studio/Controls/SkinsPanel.cs | 63 +++++++++
PCK-Studio/Controls/SkinsPanel.resx | 120 ++++++++++++++++++
PCK-Studio/PckStudio.csproj | 9 ++
PCK-Studio/Rendering/SceneViewport.cs | 8 +-
PCK-Studio/Rendering/SkinRenderer.cs | 42 +++---
.../Extensions/SkinBOXExtensions.cs | 41 ++++++
PckStudio.Core/Extensions/SkinExtensions.cs | 26 ++++
9 files changed, 409 insertions(+), 23 deletions(-)
create mode 100644 PCK-Studio/Controls/SkinsPanel.Designer.cs
create mode 100644 PCK-Studio/Controls/SkinsPanel.cs
create mode 100644 PCK-Studio/Controls/SkinsPanel.resx
diff --git a/PCK-Studio/Controls/PckAssetBrowserEditor.cs b/PCK-Studio/Controls/PckAssetBrowserEditor.cs
index a754f9c7..f0a7567a 100644
--- a/PCK-Studio/Controls/PckAssetBrowserEditor.cs
+++ b/PCK-Studio/Controls/PckAssetBrowserEditor.cs
@@ -78,6 +78,7 @@ namespace PckStudio.Controls
private readonly ViewPanel _default;
private readonly ViewPanel _models;
+ private readonly ViewPanel _skins;
private int _timesSaved = 0;
@@ -102,6 +103,7 @@ namespace PckStudio.Controls
img = found ? asset.GetTexture() : default;
return found;
}));
+ _skins = new SkinsPanel(_currentEndianness);
}
treeViewMain.TreeViewNodeSorter = new PckNodeSorter();
@@ -918,6 +920,8 @@ namespace PckStudio.Controls
{
case PckAssetType.ModelsFile:
return _models;
+ case PckAssetType.SkinDataFile:
+ return _skins;
default:
return _default;
}
diff --git a/PCK-Studio/Controls/SkinsPanel.Designer.cs b/PCK-Studio/Controls/SkinsPanel.Designer.cs
new file mode 100644
index 00000000..13b43eb7
--- /dev/null
+++ b/PCK-Studio/Controls/SkinsPanel.Designer.cs
@@ -0,0 +1,119 @@
+namespace PckStudio.Controls
+{
+ partial class SkinsPanel
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
+ this.skinRenderer1 = new PckStudio.Rendering.SkinRenderer();
+ this.treeView1 = new System.Windows.Forms.TreeView();
+ this.tableLayoutPanel1.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // tableLayoutPanel1
+ //
+ this.tableLayoutPanel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18)))));
+ this.tableLayoutPanel1.ColumnCount = 2;
+ this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
+ this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
+ this.tableLayoutPanel1.Controls.Add(this.skinRenderer1, 1, 0);
+ this.tableLayoutPanel1.Controls.Add(this.treeView1, 0, 0);
+ this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.tableLayoutPanel1.ForeColor = System.Drawing.SystemColors.Control;
+ this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
+ this.tableLayoutPanel1.Name = "tableLayoutPanel1";
+ this.tableLayoutPanel1.RowCount = 2;
+ this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
+ this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
+ this.tableLayoutPanel1.Size = new System.Drawing.Size(660, 517);
+ this.tableLayoutPanel1.TabIndex = 0;
+ //
+ // skinRenderer1
+ //
+ this.skinRenderer1.AllowCameraMovement = false;
+ this.skinRenderer1.Animate = true;
+ this.skinRenderer1.ArmorTexture = null;
+ this.skinRenderer1.BackColor = System.Drawing.Color.Black;
+ this.skinRenderer1.CapeTexture = null;
+ this.skinRenderer1.CenterOnSelect = false;
+ this.skinRenderer1.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.skinRenderer1.GuideLineColor = System.Drawing.Color.Empty;
+ this.skinRenderer1.HighlightlingColor = System.Drawing.Color.Aqua;
+ this.skinRenderer1.Location = new System.Drawing.Point(333, 3);
+ this.skinRenderer1.MouseSensetivity = 0.01F;
+ this.skinRenderer1.Name = "skinRenderer1";
+ this.skinRenderer1.RefreshRate = 60;
+ this.skinRenderer1.RenderGroundPlane = true;
+ this.skinRenderer1.RenderSkyBox = true;
+ this.skinRenderer1.SelectedIndex = -1;
+ this.skinRenderer1.SelectedIndices = new int[0];
+ this.skinRenderer1.ShowArmor = false;
+ this.skinRenderer1.ShowBoundingBox = false;
+ this.skinRenderer1.ShowGuideLines = false;
+ this.skinRenderer1.Size = new System.Drawing.Size(324, 252);
+ this.skinRenderer1.TabIndex = 0;
+ this.skinRenderer1.Texture = null;
+ this.skinRenderer1.VSync = true;
+ //
+ // treeView1
+ //
+ this.treeView1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18)))));
+ this.treeView1.BorderStyle = System.Windows.Forms.BorderStyle.None;
+ this.treeView1.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.treeView1.ForeColor = System.Drawing.SystemColors.Window;
+ this.treeView1.HideSelection = false;
+ this.treeView1.Location = new System.Drawing.Point(3, 3);
+ this.treeView1.Name = "treeView1";
+ this.treeView1.PathSeparator = ".";
+ this.tableLayoutPanel1.SetRowSpan(this.treeView1, 2);
+ this.treeView1.ShowLines = false;
+ this.treeView1.ShowPlusMinus = false;
+ this.treeView1.ShowRootLines = false;
+ this.treeView1.Size = new System.Drawing.Size(324, 511);
+ this.treeView1.TabIndex = 1;
+ this.treeView1.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.treeView1_AfterSelect);
+ //
+ // SkinsPanel
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18)))));
+ this.Controls.Add(this.tableLayoutPanel1);
+ this.Name = "SkinsPanel";
+ this.Size = new System.Drawing.Size(660, 517);
+ this.tableLayoutPanel1.ResumeLayout(false);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
+ private Rendering.SkinRenderer skinRenderer1;
+ private System.Windows.Forms.TreeView treeView1;
+ }
+}
diff --git a/PCK-Studio/Controls/SkinsPanel.cs b/PCK-Studio/Controls/SkinsPanel.cs
new file mode 100644
index 00000000..62ccf236
--- /dev/null
+++ b/PCK-Studio/Controls/SkinsPanel.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using OMI.Formats.Pck;
+using OMI.Workers.Pck;
+using PckStudio.Core.Extensions;
+using PckStudio.Core.Skin;
+
+namespace PckStudio.Controls
+{
+ public partial class SkinsPanel : ViewPanel
+ {
+ readonly PckFileReader _reader;
+ readonly ImageList _imageList;
+ public SkinsPanel(OMI.ByteOrder byteOrder)
+ {
+ InitializeComponent();
+ _reader = new PckFileReader(byteOrder);
+ _imageList = new ImageList()
+ {
+ ImageSize = new Size(32, 32),
+ ColorDepth = ColorDepth.Depth32Bit,
+ };
+ treeView1.ImageList = _imageList;
+ }
+
+ public override void LoadAsset(PckAsset asset, Action onChange)
+ {
+ Reset();
+ treeView1.Nodes.AddRange(
+ asset.GetData(_reader).GetAssetsByType(PckAssetType.SkinFile)
+ .Select(PckAssetExtensions.GetSkin)
+ .enumerate()
+ .Select(a =>
+ {
+ _imageList.Images.Add(a.value.GetPreviewImage(_imageList.ImageSize));
+ return new TreeNode(a.value.MetaData.Name) { ImageIndex = a.index, SelectedImageIndex = a.index, Tag = a.value };
+ })
+ .ToArray()
+ );
+ }
+
+ public override void Reset()
+ {
+ _imageList.Images.Clear();
+ treeView1.Nodes.Clear();
+ }
+
+ private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
+ {
+ if (e?.Node.Tag is Skin skin)
+ {
+ skinRenderer1.LoadSkin(skin);
+ }
+ }
+ }
+}
diff --git a/PCK-Studio/Controls/SkinsPanel.resx b/PCK-Studio/Controls/SkinsPanel.resx
new file mode 100644
index 00000000..1af7de15
--- /dev/null
+++ b/PCK-Studio/Controls/SkinsPanel.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/PCK-Studio/PckStudio.csproj b/PCK-Studio/PckStudio.csproj
index 6011bab2..8c62dd0b 100644
--- a/PCK-Studio/PckStudio.csproj
+++ b/PCK-Studio/PckStudio.csproj
@@ -161,6 +161,12 @@
ModelsPanel.cs
+
+ UserControl
+
+
+ SkinsPanel.cs
+
UserControl
@@ -423,6 +429,9 @@
PckAssetBrowserEditor.cs
+
+ SkinsPanel.cs
+
FilterPrompt.cs
diff --git a/PCK-Studio/Rendering/SceneViewport.cs b/PCK-Studio/Rendering/SceneViewport.cs
index ae7cca25..33ce1f45 100644
--- a/PCK-Studio/Rendering/SceneViewport.cs
+++ b/PCK-Studio/Rendering/SceneViewport.cs
@@ -66,7 +66,7 @@ namespace PckStudio.Rendering
set
{
base.BackColor = value;
- if (!DesignMode && Context.IsCurrent)
+ if (!DesignMode)
Renderer.SetClearColor(value);
}
}
@@ -149,6 +149,8 @@ namespace PckStudio.Rendering
Camera = new PerspectiveCamera(fov, camareaPosition);
_shaderLibrary = new ShaderLibrary();
+ if (!DesignMode)
+ GetContext();
}
protected virtual void Initialize() { }
@@ -171,9 +173,9 @@ namespace PckStudio.Rendering
if (DesignMode)
return;
if (!Visible)
- _timer.Stop();
+ _timer?.Stop();
if (Visible)
- _timer.Start();
+ _timer?.Start();
}
protected override void OnGotFocus(EventArgs e)
diff --git a/PCK-Studio/Rendering/SkinRenderer.cs b/PCK-Studio/Rendering/SkinRenderer.cs
index 48e778c6..cfa4a6ad 100644
--- a/PCK-Studio/Rendering/SkinRenderer.cs
+++ b/PCK-Studio/Rendering/SkinRenderer.cs
@@ -16,24 +16,23 @@
* 3. This notice may not be removed or altered from any source distribution.
**/
using System;
-using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Drawing;
using System.Diagnostics;
-using OpenTK;
-using PckStudio.Internal;
-using PckStudio.Core.Extensions;
-using OpenTK.Graphics.OpenGL;
using System.Windows.Forms;
using System.ComponentModel;
-using System.Drawing;
-using PckStudio.Properties;
+using System.Drawing.Imaging;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
-using System.Drawing.Imaging;
-using System.IO;
+using OpenTK;
+using OpenTK.Graphics.OpenGL;
+using PckStudio.Properties;
using PckStudio.Rendering.Extension;
using PckStudio.Rendering.Texture;
using PckStudio.Rendering.Shader;
-using System.Linq;
+using PckStudio.Core.Extensions;
using PckStudio.Core.Skin;
using PckStudio.Core;
@@ -183,7 +182,8 @@ namespace PckStudio.Rendering
set
{
_anim = value;
- OnANIMUpdate();
+ if (value is not null && !DesignMode)
+ OnANIMUpdate();
}
}
@@ -290,6 +290,15 @@ namespace PckStudio.Rendering
};
public SkinRenderer() : base(fov: 60f)
+ {
+ InitializeComponent();
+
+ ANIM ??= new SkinANIM(SkinAnimMask.RESOLUTION_64x64);
+ ModelData = new ObservableCollection();
+ ModelData.CollectionChanged += ModelData_CollectionChanged;
+ }
+
+ protected override void Initialize()
{
InitializeSkinData();
InitializeCapeData();
@@ -312,15 +321,6 @@ namespace PckStudio.Rendering
CalculateSkinBounds();
InitializeArmorData();
InitializeCamera();
- InitializeComponent();
-
- ANIM ??= new SkinANIM(SkinAnimMask.RESOLUTION_64x64);
- ModelData = new ObservableCollection();
- ModelData.CollectionChanged += ModelData_CollectionChanged;
- }
-
- protected override void Initialize()
- {
InitializeShaders();
Renderer.SetClearColor(BackColor);
GLErrorCheck();
@@ -755,6 +755,8 @@ namespace PckStudio.Rendering
private void OnANIMUpdate()
{
+ if (ANIM is null)
+ return;
head.SetVisible(0, !ANIM.GetFlag(SkinAnimFlag.HEAD_DISABLED));
head.SetVisible(1, !ANIM.GetFlag(SkinAnimFlag.HEAD_OVERLAY_DISABLED));
diff --git a/PckStudio.Core/Extensions/SkinBOXExtensions.cs b/PckStudio.Core/Extensions/SkinBOXExtensions.cs
index 2c95b2ba..7ed6229e 100644
--- a/PckStudio.Core/Extensions/SkinBOXExtensions.cs
+++ b/PckStudio.Core/Extensions/SkinBOXExtensions.cs
@@ -77,5 +77,46 @@ namespace PckStudio.Core.Extensions
int index = Array.IndexOf(SkinBOX.OverlayTypes, type);
return SkinBOX.BaseTypes.IndexInRange(index) ? SkinBOX.BaseTypes[index] : "";
}
+
+ public enum SkinBoxFace
+ {
+ Front,
+ Back,
+ Top,
+ Bottom,
+ Left,
+ Right
+ }
+
+ public static Rectangle GetFaceArea(this SkinBOX skinBox, SkinBoxFace face) => new Rectangle(skinBox.GetPoint(face), skinBox.GetSize(face));
+
+ public static Point GetPoint(this SkinBOX skinBox, SkinBoxFace face)
+ {
+ return Point.Truncate((face) switch
+ {
+ SkinBoxFace.Front => new PointF(skinBox.UV.X + skinBox.Size.Z , skinBox.UV.Y + skinBox.Size.Z),
+ SkinBoxFace.Back => new PointF(skinBox.UV.X + skinBox.Size.Z * 2 + skinBox.Size.X, skinBox.UV.Y + skinBox.Size.Z),
+ SkinBoxFace.Top => new PointF(skinBox.UV.X + skinBox.Size.X , skinBox.UV.Y),
+ SkinBoxFace.Bottom => new PointF(skinBox.UV.X + skinBox.Size.X * 2 , skinBox.UV.Y),
+ SkinBoxFace.Left => new PointF(skinBox.UV.X + skinBox.Size.Z + skinBox.Size.X , skinBox.UV.Y + skinBox.Size.Z),
+ SkinBoxFace.Right => new PointF(skinBox.UV.X + skinBox.Size.Z , skinBox.UV.Y + skinBox.Size.Z),
+ _ => PointF.Empty,
+ });
+ }
+
+ public static Size GetSize(this SkinBOX skinBox, SkinBoxFace face)
+ {
+ return Size.Truncate((face) switch
+ {
+ SkinBoxFace.Front => new SizeF(skinBox.Size.X, skinBox.Size.Y),
+ SkinBoxFace.Back => new SizeF(skinBox.Size.X, skinBox.Size.Y),
+ SkinBoxFace.Top => new SizeF(skinBox.Size.X, skinBox.Size.Z),
+ SkinBoxFace.Bottom => new SizeF(skinBox.Size.X, skinBox.Size.Z),
+ SkinBoxFace.Left => new SizeF(skinBox.Size.Z, skinBox.Size.Y),
+ SkinBoxFace.Right => new SizeF(skinBox.Size.Z, skinBox.Size.Y),
+ _ => SizeF.Empty,
+ });
+ }
+
}
}
diff --git a/PckStudio.Core/Extensions/SkinExtensions.cs b/PckStudio.Core/Extensions/SkinExtensions.cs
index 83887d85..6a5950fe 100644
--- a/PckStudio.Core/Extensions/SkinExtensions.cs
+++ b/PckStudio.Core/Extensions/SkinExtensions.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
@@ -68,5 +69,30 @@ namespace PckStudio.Core.Extensions
capeFile.SetTexture(capeTexture);
return capeFile;
}
+
+ public static Image GetPreviewImage(this Skin.Skin skin, Size size) => skin.GetPreviewImage(size.Width, size.Height);
+ public static Image GetPreviewImage(this Skin.Skin skin, int width = 16, int height = 16)
+ {
+ Image result = new Bitmap(width, height);
+ using Graphics g = Graphics.FromImage(result);
+ g.ApplyConfig(GraphicsConfig.PixelPerfect());
+ g.Clear(Color.Transparent);
+
+ if (!skin.Anim.GetFlag(SkinAnimFlag.HEAD_DISABLED))
+ {
+ g.DrawImage(skin.Texture.GetArea(new Rectangle(8, 8, 8, 8)), 0, 0, width, height);
+ }
+ else if (!skin.Anim.GetFlag(SkinAnimFlag.HEAD_OVERLAY_DISABLED))
+ {
+ g.DrawImage(skin.Texture.GetArea(new Rectangle(40, 8, 8, 8)), 0, 0, width, height);
+ }
+ else
+ {
+ Rectangle area = skin.Model.AdditionalBoxes.Where(sb => sb.Type == "HEAD" || sb.Type == "HEADWEAR").OrderBy(sb=> sb.Pos.Z - sb.Scale).FirstOrDefault()?.GetFaceArea(SkinBOXExtensions.SkinBoxFace.Front) ?? Rectangle.Empty;
+ Image img = skin.Texture.GetArea(area);
+ g.DrawImage(img, 0, 0, width, height);
+ }
+ return result;
+ }
}
}