Merge main into '3dSkinRenderer'

This commit is contained in:
miku-666
2024-07-20 17:55:19 +02:00
12 changed files with 125 additions and 174 deletions

View File

@@ -77,9 +77,6 @@
<setting name="AutoUpdate" serializeAs="String">
<value>False</value>
</setting>
<setting name="UseComboBoxForGRFParameter" serializeAs="String">
<value>False</value>
</setting>
<setting name="ValidateImageDimension" serializeAs="String">
<value>True</value>
</setting>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
@@ -28,25 +29,5 @@ namespace PckStudio.Extensions
ms.Position = 0;
return Image.FromStream(ms);
}
internal static JObject ConvertToJavaAnimation(this Animation animation)
{
JObject janimation = new JObject();
JObject mcmeta = new JObject();
mcmeta["comment"] = $"Animation converted with {Application.ProductName}";
mcmeta["animation"] = janimation;
JArray jframes = new JArray();
foreach (Animation.Frame frame in animation.GetFrames())
{
JObject jframe = new JObject();
jframe["index"] = animation.GetTextureIndex(frame.Texture);
jframe["time"] = frame.Ticks;
jframes.Add(jframe);
}
janimation["interpolation"] = animation.Interpolate;
janimation["frames"] = jframes;
return mcmeta;
}
}
}

View File

@@ -94,7 +94,7 @@ namespace PckStudio.Extensions
yield break;
}
internal static Image Combine(this IList<Image> sources, ImageLayoutDirection layoutDirection)
internal static Image Combine(this IEnumerable<Image> sources, ImageLayoutDirection layoutDirection)
{
Size imageSize = CalculateImageSize(sources, layoutDirection);
var image = new Bitmap(imageSize.Width, imageSize.Height);
@@ -110,24 +110,25 @@ namespace PckStudio.Extensions
return image;
}
private static Size CalculateImageSize(IList<Image> sources, ImageLayoutDirection layoutDirection)
private static Size CalculateImageSize(IEnumerable<Image> sources, ImageLayoutDirection layoutDirection)
{
if (sources.Count < 2)
{
return sources.Count < 1 ? Size.Empty : sources[0].Size;
}
var horizontal = layoutDirection == ImageLayoutDirection.Horizontal;
Size size = sources.First().Size;
int width = size.Width;
int height = size.Height;
int count = sources.Count();
int width = sources[0].Width;
int height = sources[0].Height;
if (count < 2)
return count < 1 ? Size.Empty : size;
var horizontal = layoutDirection == ImageLayoutDirection.Horizontal;
if (!sources.All(img => img.Width.Equals(width) && img.Height.Equals(height)))
throw new InvalidOperationException("Images must have the same width and height.");
if (horizontal)
width *= sources.Count;
width *= count;
else
height *= sources.Count;
height *= count;
return new Size(width, height);
}

View File

@@ -35,7 +35,6 @@
this.ValueTextBox = new MetroFramework.Controls.MetroTextBox();
this.CancelBtn = new MetroFramework.Controls.MetroButton();
this.ConfirmBtn = new MetroFramework.Controls.MetroButton();
this.availableComboBox = new MetroFramework.Controls.MetroComboBox();
metroLabel1 = new MetroFramework.Controls.MetroLabel();
metroLabel2 = new MetroFramework.Controls.MetroLabel();
this.SuspendLayout();
@@ -46,7 +45,7 @@
metroLabel1.Location = new System.Drawing.Point(18, 27);
metroLabel1.Name = "metroLabel1";
metroLabel1.Size = new System.Drawing.Size(48, 19);
metroLabel1.TabIndex = 0;
metroLabel1.TabIndex = 4;
metroLabel1.Text = "Name:";
metroLabel1.Theme = MetroFramework.MetroThemeStyle.Dark;
//
@@ -56,12 +55,14 @@
metroLabel2.Location = new System.Drawing.Point(17, 56);
metroLabel2.Name = "metroLabel2";
metroLabel2.Size = new System.Drawing.Size(42, 19);
metroLabel2.TabIndex = 1;
metroLabel2.TabIndex = 5;
metroLabel2.Text = "Value:";
metroLabel2.Theme = MetroFramework.MetroThemeStyle.Dark;
//
// NameTextBox
//
this.NameTextBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest;
this.NameTextBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource;
//
//
//
@@ -86,7 +87,7 @@
this.NameTextBox.ShortcutsEnabled = true;
this.NameTextBox.Size = new System.Drawing.Size(165, 23);
this.NameTextBox.Style = MetroFramework.MetroColorStyle.White;
this.NameTextBox.TabIndex = 2;
this.NameTextBox.TabIndex = 0;
this.NameTextBox.Theme = MetroFramework.MetroThemeStyle.Dark;
this.NameTextBox.UseSelectable = true;
this.NameTextBox.WaterMarkColor = System.Drawing.Color.FromArgb(((int)(((byte)(109)))), ((int)(((byte)(109)))), ((int)(((byte)(109)))));
@@ -117,8 +118,7 @@
this.ValueTextBox.SelectionStart = 0;
this.ValueTextBox.ShortcutsEnabled = true;
this.ValueTextBox.Size = new System.Drawing.Size(165, 23);
this.ValueTextBox.Style = MetroFramework.MetroColorStyle.White;
this.ValueTextBox.TabIndex = 3;
this.ValueTextBox.TabIndex = 1;
this.ValueTextBox.Theme = MetroFramework.MetroThemeStyle.Dark;
this.ValueTextBox.UseSelectable = true;
this.ValueTextBox.WaterMarkColor = System.Drawing.Color.FromArgb(((int)(((byte)(109)))), ((int)(((byte)(109)))), ((int)(((byte)(109)))));
@@ -131,7 +131,7 @@
this.CancelBtn.Name = "CancelBtn";
this.CancelBtn.Size = new System.Drawing.Size(95, 23);
this.CancelBtn.Style = MetroFramework.MetroColorStyle.White;
this.CancelBtn.TabIndex = 4;
this.CancelBtn.TabIndex = 2;
this.CancelBtn.Text = "Cancel";
this.CancelBtn.Theme = MetroFramework.MetroThemeStyle.Dark;
this.CancelBtn.UseSelectable = true;
@@ -142,33 +142,18 @@
this.ConfirmBtn.Name = "ConfirmBtn";
this.ConfirmBtn.Size = new System.Drawing.Size(96, 23);
this.ConfirmBtn.Style = MetroFramework.MetroColorStyle.White;
this.ConfirmBtn.TabIndex = 5;
this.ConfirmBtn.TabIndex = 3;
this.ConfirmBtn.Text = "Confirm";
this.ConfirmBtn.Theme = MetroFramework.MetroThemeStyle.Dark;
this.ConfirmBtn.UseSelectable = true;
this.ConfirmBtn.Click += new System.EventHandler(this.ConfirmButton_Click);
//
// availableComboBox
//
this.availableComboBox.FormattingEnabled = true;
this.availableComboBox.ItemHeight = 23;
this.availableComboBox.Location = new System.Drawing.Point(72, 21);
this.availableComboBox.Name = "availableComboBox";
this.availableComboBox.Size = new System.Drawing.Size(165, 29);
this.availableComboBox.Style = MetroFramework.MetroColorStyle.Silver;
this.availableComboBox.TabIndex = 6;
this.availableComboBox.Theme = MetroFramework.MetroThemeStyle.Dark;
this.availableComboBox.UseSelectable = true;
this.availableComboBox.Visible = false;
this.availableComboBox.SelectedIndexChanged += new System.EventHandler(this.availableComboBox_SelectedIndexChanged);
//
// AddParameter
//
this.AcceptButton = this.ConfirmBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(264, 126);
this.Controls.Add(this.availableComboBox);
this.Controls.Add(this.ConfirmBtn);
this.Controls.Add(this.CancelBtn);
this.Controls.Add(this.ValueTextBox);
@@ -192,6 +177,5 @@
private MetroFramework.Controls.MetroButton CancelBtn;
private MetroFramework.Controls.MetroButton ConfirmBtn;
private MetroFramework.Controls.MetroTextBox NameTextBox;
private MetroFramework.Controls.MetroComboBox availableComboBox;
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Windows.Forms;
using OMI.Formats.GameRule;
using PckStudio.Properties;
namespace PckStudio.Forms.Additional_Popups.Grf
{
@@ -10,25 +9,12 @@ namespace PckStudio.Forms.Additional_Popups.Grf
public string ParameterName => NameTextBox.Text;
public string ParameterValue => ValueTextBox.Text;
private bool _useComboBox
{
get
{
return availableComboBox.Visible && !NameTextBox.Visible;
}
set
{
NameTextBox.Visible = !value;
availableComboBox.Visible = value;
}
}
public AddParameter()
{
InitializeComponent();
availableComboBox.Items.Clear();
availableComboBox.Items.AddRange(GameRuleFile.GameRule.ValidParameters);
_useComboBox = Settings.Default.UseComboBoxForGRFParameter;
NameTextBox.AutoCompleteCustomSource = new AutoCompleteStringCollection();
NameTextBox.AutoCompleteCustomSource.AddRange(GameRuleFile.GameRule.ValidParameters);
}
public AddParameter(string parameterName, string parameterValue, bool isKeyReadonly = true) : this()
@@ -36,23 +22,16 @@ namespace PckStudio.Forms.Additional_Popups.Grf
NameTextBox.Text = parameterName;
ValueTextBox.Text = parameterValue;
NameTextBox.Enabled = isKeyReadonly;
availableComboBox.Enabled = isKeyReadonly;
availableComboBox.SelectedItem = parameterName;
}
private void ConfirmButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(ParameterName) || string.IsNullOrWhiteSpace(ParameterValue))
{
MessageBox.Show(this, "Name and Value need valid values");
MessageBox.Show(this, "Name or value can't be empty.", "Empty value", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
return;
}
DialogResult = DialogResult.OK;
}
private void availableComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
NameTextBox.Text = availableComboBox.SelectedItem.ToString();
}
}
}

View File

@@ -33,7 +33,6 @@ namespace PckStudio.Forms
["AutoUpdate"] = "Auto Update",
["LoadSubPcks"] = "Load Sub Pcks",
["UsePrerelease"] = "Use Prerelease",
["UseComboBoxForGRFParameter"] = "Easy Grf Param",
};
private void CheckBox_CheckedChanged(object sender, EventArgs e)

View File

@@ -33,6 +33,7 @@ using PckStudio.Extensions;
using PckStudio.Properties;
using PckStudio.Internal;
using PckStudio.Internal.Deserializer;
using PckStudio.Internal.Serializer;
namespace PckStudio.Forms.Editor
{
@@ -336,7 +337,6 @@ namespace PckStudio.Forms.Editor
var img = Image.FromFile(textureFile).ReleaseFromFile();
JObject mcmeta = JObject.Parse(File.ReadAllText(fileDialog.FileName));
Animation javaAnimation = AnimationDeserializer.DefaultDeserializer.DeserializeJavaAnimation(mcmeta, img);
//javaAnimation.Category = _animation.Category;
_animation = javaAnimation;
LoadAnimationTreeView();
}
@@ -354,11 +354,11 @@ namespace PckStudio.Forms.Editor
fileDialog.Filter = "Animation Scripts (*.mcmeta)|*.png.mcmeta";
if (fileDialog.ShowDialog(this) == DialogResult.OK)
{
JObject mcmeta = _animation.ConvertToJavaAnimation();
JObject mcmeta = AnimationSerializer.SerializeJavaAnimation(_animation);
string jsondata = JsonConvert.SerializeObject(mcmeta, Formatting.Indented);
string filename = fileDialog.FileName;
File.WriteAllText(filename, jsondata);
Image finalTexture = _animation.BuildTexture();
Image finalTexture = AnimationSerializer.SerializeTexture(_animation);
// removes ".mcmeta" from filename
string texturePath = Path.Combine(Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(filename));
finalTexture.Save(texturePath);
@@ -422,7 +422,7 @@ namespace PckStudio.Forms.Editor
return;
}
var oldResolution = _animation.BuildTexture().Width;
var oldResolution = _animation.GetFrame(0).Texture.Width;
FrameDimension dimension = new FrameDimension(gif.FrameDimensionsList[0]);
int frameCount = gif.GetFrameCount(dimension);
@@ -436,10 +436,7 @@ namespace PckStudio.Forms.Editor
textures.Add(new Bitmap(gif, oldResolution, oldResolution));
}
// TODO: Add function or a other way to initialize the frames by textures.
// Currently single frames only get added when an anim has an invalid format or is empty.
// -Miku
_animation = new Animation(textures, "");
_animation = new Animation(textures, initFramesFromTextures: true);
_animation.Interpolate = InterpolationCheckbox.Checked;
LoadAnimationTreeView();
}
@@ -453,9 +450,9 @@ namespace PckStudio.Forms.Editor
};
if (ofd.ShowDialog(this) != DialogResult.OK)
return;
Image img = Image.FromFile(ofd.FileName).ReleaseFromFile();
var textures = img.Split(ImageLayoutDirection.Vertical);
_animation = new Animation(textures, string.Empty);
using Image img = Image.FromFile(ofd.FileName);
IEnumerable<Image> textures = img.Split(ImageLayoutDirection.Vertical);
_animation = new Animation(textures, initFramesFromTextures: true);
LoadAnimationTreeView();
}

View File

@@ -22,6 +22,7 @@ using PckStudio.Extensions;
using System.Text;
using System.Collections.ObjectModel;
using System.Linq;
using System.Diagnostics;
namespace PckStudio.Internal
{
@@ -41,17 +42,13 @@ namespace PckStudio.Internal
private object _syncLock = new object();
public Animation(IEnumerable<Image> textures)
public Animation(IEnumerable<Image> textures, bool initFramesFromTextures = false)
{
_textures = new List<Image>(textures);
if (initFramesFromTextures)
AddTexturesAsFrames(MinimumFrameTime);
}
public Animation(IEnumerable<Image> textures, string animString)
: this(textures)
{
ParseAnim(animString);
}
public class Frame
{
public readonly Image Texture;
@@ -83,47 +80,12 @@ namespace PckStudio.Internal
}
}
private void ParseAnim(string anim)
{
if (string.IsNullOrEmpty(anim))
{
AddSingleFrames();
return;
}
anim = anim.Trim();
anim = (Interpolate = anim.StartsWith("#")) ? anim.Substring(1) : anim;
string[] animData = anim.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
int lastFrameTime = MinimumFrameTime;
if (animData.Length <= 0)
{
AddSingleFrames();
return;
}
foreach (string frameInfo in animData)
{
string[] frameData = frameInfo.Split('*');
int currentFrameIndex = 0;
int.TryParse(frameData[0], out currentFrameIndex);
// Some textures like the Halloween 2015's Lava texture don't have a
// frame time parameter for certain frames.
// This will detect that and place the last frame time in its place.
// This is accurate to console edition behavior.
// - MattNL
int currentFrameTime = frameData.Length < 2 || string.IsNullOrEmpty(frameData[1]) ? lastFrameTime : int.Parse(frameData[1]);
AddFrame(currentFrameIndex, currentFrameTime);
lastFrameTime = currentFrameTime;
}
}
private void CheckTextureIndex(int index)
{
if (!_textures.IndexInRange(index))
throw new ArgumentOutOfRangeException(nameof(index));
}
public Frame AddFrame(int textureIndex) => AddFrame(textureIndex, MinimumFrameTime);
public Frame AddFrame(int textureIndex, int frameTime)
{
CheckTextureIndex(textureIndex);
@@ -132,11 +94,11 @@ namespace PckStudio.Internal
return frame;
}
private void AddSingleFrames()
private void AddTexturesAsFrames(int frameTime)
{
for (int i = 0; i < TextureCount; i++)
{
AddFrame(i);
AddFrame(i, frameTime);
}
}
@@ -205,22 +167,6 @@ namespace PckStudio.Internal
SetFrame(frameIndex, new Frame(_textures[textureIndex], frameTime));
}
public string BuildAnim()
{
StringBuilder stringBuilder = new StringBuilder(Interpolate ? "#" : string.Empty);
foreach (Frame frame in _frames)
stringBuilder.Append($"{GetTextureIndex(frame.Texture)}*{frame.Ticks},");
return stringBuilder.ToString(0, stringBuilder.Length - 1);
}
public Image BuildTexture()
{
if (_textures[0].Width != _textures[0].Height)
throw new Exception("Invalid size");
return _textures.Combine(ImageLayoutDirection.Vertical);
}
internal void SetFrameTicks(int ticks)
{
lock(_syncLock)

View File

@@ -23,12 +23,51 @@ namespace PckStudio.Internal.Deserializer
{
Image texture = asset.GetTexture();
IEnumerable<Image> frameTextures = texture.Split(ImageLayoutDirection.Vertical);
var _animation = new Animation(frameTextures, asset.GetProperty("ANIM"));
return _animation;
Animation animation = new Animation(frameTextures);
DeserializeAnimationAnim(ref animation, asset.GetProperty("ANIM"));
return animation;
}
return Animation.CreateEmpty();
}
private static bool DeserializeAnimationAnim(ref Animation animation, string animString)
{
if (string.IsNullOrEmpty(animString))
{
Trace.TraceError($"[{nameof(AnimationExtensions)}:{nameof(DeserializeAnimationAnim)}] Failed to parse anim. anim was null or empty.");
return false;
}
animString = animString.Trim();
animation.Interpolate = animString.StartsWith("#");
animString = animation.Interpolate ? animString.Substring(1) : animString;
string[] animData = animString.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (animData.Length <= 0)
{
Trace.TraceError($"[{nameof(AnimationExtensions)}:{nameof(DeserializeAnimationAnim)}] Failed to parse anim. animData was empty.");
return false;
}
int lastFrameTime = Animation.MinimumFrameTime;
foreach (string frameInfo in animData)
{
string[] frameData = frameInfo.Split('*');
int currentFrameIndex = 0;
int.TryParse(frameData[0], out currentFrameIndex);
// Some textures like the Halloween 2015's Lava texture don't have a
// frame time parameter for certain frames.
// This will detect that and place the last frame time in its place.
// This is accurate to console edition behavior.
// - MattNL
int currentFrameTime = frameData.Length < 2 || string.IsNullOrEmpty(frameData[1]) ? lastFrameTime : int.Parse(frameData[1]);
animation.AddFrame(currentFrameIndex, currentFrameTime);
lastFrameTime = currentFrameTime;
}
return true;
}
public Animation DeserializeJavaAnimation(JObject jsonObject, Image texture)
{
IEnumerable<Image> textures = texture.Split(ImageLayoutDirection.Vertical);

View File

@@ -15,10 +15,16 @@
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
using System.Collections.Generic;
using System;
using System.Drawing;
using System.Text;
using OMI.Formats.Pck;
using PckStudio.Extensions;
using PckStudio.Interfaces;
using System.Linq;
using Newtonsoft.Json.Linq;
using System.Windows.Forms;
namespace PckStudio.Internal.Serializer
{
@@ -28,10 +34,47 @@ namespace PckStudio.Internal.Serializer
public void Serialize(Animation animation, ref PckAsset asset)
{
string anim = animation.BuildAnim();
string anim = SerializeAnim(animation);
asset.SetProperty("ANIM", anim);
Image texture = animation.BuildTexture();
Image texture = SerializeTexture(animation);
asset.SetTexture(texture);
}
private static string SerializeAnim(Animation animation)
{
StringBuilder stringBuilder = new StringBuilder(animation.Interpolate ? "#" : string.Empty);
foreach (Animation.Frame frame in animation.GetFrames())
stringBuilder.Append($"{animation.GetTextureIndex(frame.Texture)}*{frame.Ticks},");
return stringBuilder.ToString(0, stringBuilder.Length - 1);
}
internal static Image SerializeTexture(Animation animation)
{
IReadOnlyCollection<Image> textures = animation.GetTextures();
Size size = textures.First().Size;
if (size.Width != size.Height)
throw new Exception("Invalid size");
return textures.Combine(ImageLayoutDirection.Vertical);
}
internal static JObject SerializeJavaAnimation(Animation animation)
{
JObject janimation = new JObject();
JObject mcmeta = new JObject();
mcmeta["comment"] = $"Animation converted with {Application.ProductName}";
mcmeta["animation"] = janimation;
JArray jframes = new JArray();
foreach (Animation.Frame frame in animation.GetFrames())
{
JObject jframe = new JObject();
jframe["index"] = animation.GetTextureIndex(frame.Texture);
jframe["time"] = frame.Ticks;
jframes.Add(jframe);
}
janimation["interpolation"] = animation.Interpolate;
janimation["frames"] = jframes;
return mcmeta;
}
}
}

View File

@@ -12,7 +12,7 @@ namespace PckStudio.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.9.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@@ -104,18 +104,6 @@ namespace PckStudio.Properties {
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool UseComboBoxForGRFParameter {
get {
return ((bool)(this["UseComboBoxForGRFParameter"]));
}
set {
this["UseComboBoxForGRFParameter"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.Specialized.StringCollection RecentFiles {

View File

@@ -23,9 +23,6 @@
<Setting Name="AutoUpdate" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="UseComboBoxForGRFParameter" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="RecentFiles" Type="System.Collections.Specialized.StringCollection" Scope="User">
<Value Profile="(Default)" />
</Setting>