mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/PCK-Studio.git
synced 2026-06-20 11:26:12 +00:00
Move Common functionality to Core project & rendering and Model support as well
This commit is contained in:
193
PckStudio.Core/Animation.cs
Normal file
193
PckStudio.Core/Animation.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
/* Copyright (c) 2023-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using PckStudio.Core.Extensions;
|
||||
using System.Text;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public sealed class Animation
|
||||
{
|
||||
public const int MinimumFrameTime = 1;
|
||||
|
||||
public int FrameCount => _frames.Count;
|
||||
|
||||
public int TextureCount => _textures.Count;
|
||||
|
||||
public bool Interpolate { get; set; } = false;
|
||||
|
||||
private readonly List<Image> _textures;
|
||||
|
||||
private readonly IList<Frame> _frames = new List<Frame>();
|
||||
|
||||
private object _syncLock = new object();
|
||||
|
||||
public Animation(IEnumerable<Image> textures, bool initFramesFromTextures = false)
|
||||
{
|
||||
_textures = new List<Image>(textures);
|
||||
if (initFramesFromTextures)
|
||||
AddTexturesAsFrames(MinimumFrameTime);
|
||||
}
|
||||
|
||||
public class Frame
|
||||
{
|
||||
public readonly Image Texture;
|
||||
public int Ticks
|
||||
{
|
||||
get
|
||||
{
|
||||
return _ticks;
|
||||
}
|
||||
set
|
||||
{
|
||||
lock(_syncObject)
|
||||
{
|
||||
_ticks = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _ticks;
|
||||
private object _syncObject = new object();
|
||||
|
||||
public Frame(Image texture) : this(texture, MinimumFrameTime)
|
||||
{ }
|
||||
|
||||
public Frame(Image texture, int frameTime)
|
||||
{
|
||||
Texture = texture;
|
||||
Ticks = frameTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckTextureIndex(int index)
|
||||
{
|
||||
if (!_textures.IndexInRange(index))
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
public Frame AddFrame(int textureIndex, int frameTime)
|
||||
{
|
||||
CheckTextureIndex(textureIndex);
|
||||
Frame frame = new Frame(_textures[textureIndex], frameTime);
|
||||
_frames.Add(frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
private void AddTexturesAsFrames(int frameTime)
|
||||
{
|
||||
for (int i = 0; i < TextureCount; i++)
|
||||
{
|
||||
AddFrame(i, frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveFrame(int frameIndex)
|
||||
{
|
||||
_frames.RemoveAt(frameIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Frame GetFrame(int index) => _frames[index];
|
||||
|
||||
public IReadOnlyCollection<Frame> GetFrames()
|
||||
{
|
||||
return new ReadOnlyCollection<Frame>(_frames);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<Frame> GetInterpolatedFrames()
|
||||
{
|
||||
if (Interpolate)
|
||||
{
|
||||
return new ReadOnlyCollection<Frame>(InternalGetInterpolatedFrames().ToList());
|
||||
}
|
||||
return GetFrames();
|
||||
}
|
||||
|
||||
private IEnumerable<Frame> InternalGetInterpolatedFrames()
|
||||
{
|
||||
for (int i = 0; i < FrameCount; i++)
|
||||
{
|
||||
Frame currentFrame = _frames[i];
|
||||
Frame nextFrame = _frames[0];
|
||||
if (i + 1 < FrameCount)
|
||||
nextFrame = _frames[i + 1];
|
||||
for (int tick = 0; tick < currentFrame.Ticks; tick++)
|
||||
{
|
||||
double delta = 1.0f - tick / (double)currentFrame.Ticks;
|
||||
yield return new Frame(currentFrame.Texture.Interpolate(nextFrame.Texture, delta));
|
||||
}
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
|
||||
public IReadOnlyCollection<Image> GetTextures()
|
||||
{
|
||||
return _textures;
|
||||
}
|
||||
|
||||
public int GetTextureIndex(Image frameTexture)
|
||||
{
|
||||
_ = frameTexture ?? throw new ArgumentNullException(nameof(frameTexture));
|
||||
return _textures.IndexOf(frameTexture);
|
||||
}
|
||||
|
||||
public void SetFrame(int frameIndex, Frame frame)
|
||||
{
|
||||
lock(_syncLock)
|
||||
{
|
||||
_frames[frameIndex] = frame;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFrame(int frameIndex, int textureIndex, int frameTime = MinimumFrameTime)
|
||||
{
|
||||
CheckTextureIndex(textureIndex);
|
||||
SetFrame(frameIndex, new Frame(_textures[textureIndex], frameTime));
|
||||
}
|
||||
|
||||
public void SetFrameTicks(int ticks)
|
||||
{
|
||||
lock(_syncLock)
|
||||
{
|
||||
foreach (Frame frame in _frames)
|
||||
{
|
||||
frame.Ticks = ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SwapFrames(int sourceIndex, int destinationIndex)
|
||||
{
|
||||
lock(_syncLock)
|
||||
{
|
||||
_frames.Swap(sourceIndex, destinationIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Animation CreateEmpty()
|
||||
{
|
||||
return new Animation(Array.Empty<Image>());
|
||||
}
|
||||
}
|
||||
}
|
||||
113
PckStudio.Core/App/SettingsManager.cs
Normal file
113
PckStudio.Core/App/SettingsManager.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
/* Copyright (c) 2023-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
|
||||
namespace PckStudio.Core.App
|
||||
{
|
||||
public sealed class SettingsManager
|
||||
{
|
||||
public bool IsReadOnly => _isReadOnly;
|
||||
|
||||
public const string KeyToStringContextKeyConst = "keyToString";
|
||||
|
||||
private Dictionary<string, Action<object>> _registery = new Dictionary<string, Action<object>>();
|
||||
|
||||
private object _newValue = null;
|
||||
private ApplicationSettingsBase _settings = null;
|
||||
private bool _isReadOnly;
|
||||
|
||||
private class InternalSettings : ApplicationSettingsBase
|
||||
{ }
|
||||
|
||||
public SettingsManager(ApplicationSettingsBase settings, bool isReadOnly = false)
|
||||
{
|
||||
_settings = settings;
|
||||
_isReadOnly = isReadOnly;
|
||||
settings.PropertyChanged += PropertyChangedHandler;
|
||||
settings.SettingChanging += SettingChangingHandler;
|
||||
}
|
||||
|
||||
public ApplicationSettingsBase GetSettings() => _settings;
|
||||
|
||||
public static SettingsManager CreateSettings()
|
||||
{
|
||||
return new SettingsManager(new InternalSettings());
|
||||
}
|
||||
|
||||
public bool RegisterPropertyChangedCallback<TSettingsType>(string propertyName, Action<TSettingsType> callback)
|
||||
{
|
||||
Type propertyType = _settings[propertyName].GetType();
|
||||
if (!propertyType.Equals(typeof(TSettingsType)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return RegisterPropertyChangedCallback(propertyName, delegate (object obj) { callback((TSettingsType)obj); });
|
||||
}
|
||||
|
||||
public bool RegisterPropertyChangedCallback(string propertyName, Action callback)
|
||||
{
|
||||
return RegisterPropertyChangedCallback(propertyName, delegate (object _) { callback(); });
|
||||
}
|
||||
|
||||
private bool RegisterPropertyChangedCallback(string propertyName, Action<object> callback)
|
||||
{
|
||||
if (_registery.ContainsKey(propertyName))
|
||||
return false;
|
||||
_registery.Add(propertyName, callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (_registery.ContainsKey(e.PropertyName))
|
||||
{
|
||||
_registery[e.PropertyName]?.Invoke(_newValue);
|
||||
_newValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void SettingChangingHandler(object sender, SettingChangingEventArgs e)
|
||||
{
|
||||
if (_registery.ContainsKey(e.SettingName))
|
||||
{
|
||||
_newValue = e.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddSetting<T>(string name, T initialValue, string description, Action<T> callback)
|
||||
{
|
||||
if (_isReadOnly)
|
||||
throw new SettingsPropertyIsReadOnlyException("Can't add setting. Underlying SettingsBase is readonly.");
|
||||
|
||||
if (!_settings.Context.ContainsKey(KeyToStringContextKeyConst))
|
||||
_settings.Context.Add(KeyToStringContextKeyConst, new Dictionary<string, string>());
|
||||
|
||||
var settingsProperty = new SettingsProperty(
|
||||
name, typeof(T), null, false, default(T), SettingsSerializeAs.String, null, false, false);
|
||||
_settings.Properties.Add(settingsProperty);
|
||||
_settings.PropertyValues.Add(new SettingsPropertyValue(settingsProperty) { PropertyValue = initialValue });
|
||||
if (_settings.Context[KeyToStringContextKeyConst] is Dictionary<string, string> dict)
|
||||
dict.Add(name, description);
|
||||
callback(initialValue);
|
||||
return RegisterPropertyChangedCallback(name, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
PckStudio.Core/App/Updater.cs
Normal file
63
PckStudio.Core/App/Updater.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using AutoUpdaterDotNET;
|
||||
using Newtonsoft.Json;
|
||||
using PckStudio.Core.Json;
|
||||
|
||||
namespace PckStudio.Core.App
|
||||
{
|
||||
public static class Updater
|
||||
{
|
||||
private static Uri _appCast;
|
||||
private static bool _autoUpdate;
|
||||
|
||||
public static void Initialize(Uri appCast, bool autoUpdate = false)
|
||||
{
|
||||
_appCast = appCast;
|
||||
_autoUpdate = autoUpdate;
|
||||
//AutoUpdater.ClearAppDirectory = true;
|
||||
#if DEBUG
|
||||
AutoUpdater.ReportErrors = true;
|
||||
#endif
|
||||
AutoUpdater.DownloadPath = Application.StartupPath;
|
||||
AutoUpdater.ExecutablePath = "./PCK-Studio.exe";
|
||||
AutoUpdater.TopMost = true;
|
||||
|
||||
string jsonPath = Path.Combine(Environment.CurrentDirectory, "updates.json");
|
||||
AutoUpdater.PersistenceProvider = new JsonFilePersistenceProvider(jsonPath);
|
||||
AutoUpdater.ParseUpdateInfoEvent += AutoUpdaterOnParseUpdateInfoEvent;
|
||||
//AutoUpdater.Icon = Resources.ProjectLogo.ToBitmap();
|
||||
|
||||
if (_autoUpdate)
|
||||
{
|
||||
UpdateToLatest();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetOwner(Form owner) => AutoUpdater.SetOwner(owner);
|
||||
|
||||
public static void UpdateToLatest()
|
||||
{
|
||||
#if NDEBUG
|
||||
string url = $"{_appCast}/main/Version.json";
|
||||
AutoUpdater.Start(url);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void AutoUpdaterOnParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
|
||||
{
|
||||
UpdateInformation json = JsonConvert.DeserializeObject<UpdateInformation>(args.RemoteData);
|
||||
args.UpdateInfo = new UpdateInfoEventArgs
|
||||
{
|
||||
CurrentVersion = json.Version,
|
||||
DownloadURL = json.Url,
|
||||
ChangelogURL = json.Changelog,
|
||||
Mandatory = new Mandatory()
|
||||
{
|
||||
Value = json.Mandatory,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
37
PckStudio.Core/BoundingBox.cs
Normal file
37
PckStudio.Core/BoundingBox.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using PckStudio.Core.Extensions;
|
||||
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public struct BoundingBox
|
||||
{
|
||||
public static BoundingBox Empty = new BoundingBox(OpenTK.Vector3.Zero, OpenTK.Vector3.Zero);
|
||||
|
||||
public readonly OpenTK.Vector3 Start;
|
||||
public readonly OpenTK.Vector3 End;
|
||||
public readonly OpenTK.Vector3 Center;
|
||||
public readonly OpenTK.Vector3 Volume;
|
||||
|
||||
public BoundingBox(OpenTK.Vector3 start, OpenTK.Vector3 end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
OpenTK.Vector3 size = End - Start;
|
||||
Volume = OpenTKExtensions.Abs(size);
|
||||
Center = start + Volume / 2;
|
||||
}
|
||||
|
||||
public BoundingBox(System.Numerics.Vector3 start, System.Numerics.Vector3 end)
|
||||
: this(start.ToOpenTKVector(), end.ToOpenTKVector())
|
||||
{
|
||||
}
|
||||
|
||||
public OpenTK.Matrix4 GetTransform()
|
||||
{
|
||||
return OpenTK.Matrix4.CreateScale(Volume) * OpenTK.Matrix4.CreateTranslation(Start);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
PckStudio.Core/DelegatedFileSaveContext.cs
Normal file
47
PckStudio.Core/DelegatedFileSaveContext.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PckStudio.Interfaces;
|
||||
using PckStudio.Core;
|
||||
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public sealed class DelegatedFileSaveContext<T> : ISaveContext<T>
|
||||
{
|
||||
public delegate void SerializeDataToStreamDelegate(T value, Stream stream);
|
||||
|
||||
public bool AutoSave { get; }
|
||||
public string Filepath { get; private set; }
|
||||
private SerializeDataToStreamDelegate _serializeDataDelegate;
|
||||
private FileDialogFilter _dialogFilter;
|
||||
|
||||
public DelegatedFileSaveContext(string filepath, bool autoSave, FileDialogFilter dialogFilter, SerializeDataToStreamDelegate serializeDataDelegate)
|
||||
{
|
||||
AutoSave = autoSave;
|
||||
Filepath = filepath;
|
||||
_serializeDataDelegate = serializeDataDelegate;
|
||||
_dialogFilter = dialogFilter;
|
||||
}
|
||||
|
||||
public void Save(T value)
|
||||
{
|
||||
if (!File.Exists(Filepath))
|
||||
{
|
||||
SaveFileDialog saveFileDialog = new SaveFileDialog();
|
||||
saveFileDialog.Filter = _dialogFilter.ToString();
|
||||
if (saveFileDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
Filepath = saveFileDialog.FileName;
|
||||
}
|
||||
using (Stream stream = File.OpenWrite(Filepath))
|
||||
{
|
||||
_serializeDataDelegate(value, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
PckStudio.Core/DelegatedSaveContext.cs
Normal file
19
PckStudio.Core/DelegatedSaveContext.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using PckStudio.Interfaces;
|
||||
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public class DelegatedSaveContext<T> : ISaveContext<T>
|
||||
{
|
||||
private readonly Action<T> _saveAction;
|
||||
public bool AutoSave { get; }
|
||||
|
||||
public void Save(T value) => _saveAction(value);
|
||||
|
||||
public DelegatedSaveContext(bool autoSave, Action<T> saveAction)
|
||||
{
|
||||
AutoSave = autoSave;
|
||||
_saveAction = saveAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
115
PckStudio.Core/Deserializer/AnimationDeserializer.cs
Normal file
115
PckStudio.Core/Deserializer/AnimationDeserializer.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OMI.Formats.Pck;
|
||||
using PckStudio.Core;
|
||||
using PckStudio.Core.Extensions;
|
||||
using PckStudio.Interfaces;
|
||||
|
||||
namespace PckStudio.Core.Deserializer
|
||||
{
|
||||
public sealed class AnimationDeserializer : IPckAssetDeserializer<Animation>
|
||||
{
|
||||
public static readonly AnimationDeserializer DefaultDeserializer = new AnimationDeserializer();
|
||||
|
||||
public Animation Deserialize(PckAsset asset)
|
||||
{
|
||||
_ = asset ?? throw new ArgumentNullException(nameof(asset));
|
||||
if (asset.Size > 0)
|
||||
{
|
||||
Image texture = asset.GetTexture();
|
||||
IEnumerable<Image> frameTextures = texture.Split(ImageLayoutDirection.Vertical);
|
||||
string animString = asset.GetProperty("ANIM");
|
||||
bool animStringIsEmpty = string.IsNullOrEmpty(animString);
|
||||
Animation animation = new Animation(frameTextures, animStringIsEmpty);
|
||||
if (!animStringIsEmpty)
|
||||
DeserializeAnimationAnim(ref animation, animString);
|
||||
return animation;
|
||||
}
|
||||
return Animation.CreateEmpty();
|
||||
}
|
||||
|
||||
private static bool DeserializeAnimationAnim(ref Animation animation, string animString)
|
||||
{
|
||||
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);
|
||||
Animation result = new Animation(textures);
|
||||
if (jsonObject["animation"] is not JToken animation)
|
||||
return result;
|
||||
|
||||
int frameTime = Animation.MinimumFrameTime;
|
||||
|
||||
if (animation["frametime"] is JToken frametime_token && frametime_token.Type == JTokenType.Integer)
|
||||
frameTime = (int)frametime_token;
|
||||
|
||||
if (animation["interpolate"] is JToken interpolate_token && interpolate_token.Type == JTokenType.Boolean)
|
||||
result.Interpolate = (bool)interpolate_token;
|
||||
|
||||
if (animation["frames"] is JToken frames_token && frames_token.Type == JTokenType.Array)
|
||||
{
|
||||
foreach (JToken frame in frames_token.Children())
|
||||
{
|
||||
if (frame.Type == JTokenType.Object &&
|
||||
frame["index"] is JToken frame_index &&
|
||||
frame_index.Type == JTokenType.Integer &&
|
||||
frame["time"] is JToken frame_time &&
|
||||
frame_time.Type == JTokenType.Integer)
|
||||
{
|
||||
Debug.WriteLine("Index: {0}, Time: {1}", frame_index, frame_time);
|
||||
result.AddFrame((int)frame_index, (int)frame_time);
|
||||
}
|
||||
else if (frame.Type == JTokenType.Integer)
|
||||
{
|
||||
Debug.WriteLine("Index: {0}, Time: {1}", frame, frameTime);
|
||||
result.AddFrame((int)frame, frameTime);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
for (int i = 0; i < result.TextureCount; i++)
|
||||
{
|
||||
result.AddFrame(i, frameTime);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
PckStudio.Core/Deserializer/ImageDeserializer.cs
Normal file
31
PckStudio.Core/Deserializer/ImageDeserializer.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using OMI.Formats.Pck;
|
||||
using PckStudio.Interfaces;
|
||||
using PckStudio.Core.IO.TGA;
|
||||
|
||||
namespace PckStudio.Core.Deserializer
|
||||
{
|
||||
internal sealed class ImageDeserializer : IPckAssetDeserializer<Image>
|
||||
{
|
||||
public static readonly ImageDeserializer DefaultDeserializer = new ImageDeserializer();
|
||||
// TODO: replace empty image with image displaying something went wrong
|
||||
private static Image EmptyImage = new Bitmap(1, 1, PixelFormat.Format32bppArgb);
|
||||
|
||||
public Image Deserialize(PckAsset asset)
|
||||
{
|
||||
_ = asset ?? throw new ArgumentNullException(nameof(asset));
|
||||
if (asset.Size == 0)
|
||||
return EmptyImage;
|
||||
|
||||
using var stream = new MemoryStream(asset.Data);
|
||||
|
||||
Image img = Path.GetExtension(asset.Filename) == ".tga"
|
||||
? TGADeserializer.DeserializeFromStream(stream)
|
||||
: Image.FromStream(stream);
|
||||
return img.RawFormat != ImageFormat.Jpeg || img.RawFormat != ImageFormat.Png ? new Bitmap(img) : img;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
PckStudio.Core/Extensions/AnimationExtensions.cs
Normal file
24
PckStudio.Core/Extensions/AnimationExtensions.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Drawing;
|
||||
using AnimatedGif;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class AnimationExtensions
|
||||
{
|
||||
public static Image CreateAnimationImage(this Animation animation)
|
||||
{
|
||||
if (animation.FrameCount == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var ms = new System.IO.MemoryStream();
|
||||
var generateor = new AnimatedGifCreator(ms, GameConstants.GameTickInMilliseconds, 0);
|
||||
foreach (Animation.Frame frame in animation.GetInterpolatedFrames())
|
||||
{
|
||||
generateor.AddFrame(frame.Texture, frame.Ticks * GameConstants.GameTickInMilliseconds, GifQuality.Bit8);
|
||||
}
|
||||
ms.Position = 0;
|
||||
return Image.FromStream(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
PckStudio.Core/Extensions/BlendMode.cs
Normal file
14
PckStudio.Core/Extensions/BlendMode.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public enum BlendMode
|
||||
{
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Average,
|
||||
DescendingOrder,
|
||||
AscendingOrder,
|
||||
Screen,
|
||||
Overlay
|
||||
}
|
||||
}
|
||||
16
PckStudio.Core/Extensions/BoundingBoxExtensions.cs
Normal file
16
PckStudio.Core/Extensions/BoundingBoxExtensions.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class BoundingBoxExtensions
|
||||
{
|
||||
public static BoundingBox GetEnclosingBoundingBox(this IEnumerable<BoundingBox> boundingBoxes)
|
||||
{
|
||||
return boundingBoxes.DefaultIfEmpty().Aggregate((a, b) => new BoundingBox(OpenTK.Vector3.ComponentMin(a.Start, b.Start), OpenTK.Vector3.ComponentMax(a.End, b.End)));
|
||||
}
|
||||
}
|
||||
}
|
||||
73
PckStudio.Core/Extensions/ColorExtensions.cs
Normal file
73
PckStudio.Core/Extensions/ColorExtensions.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class ColorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Normalizes the Color between 0.0 - 1.0
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Vector4 Normalize(this Color color)
|
||||
{
|
||||
return new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
|
||||
}
|
||||
|
||||
public static Color Inversed(this Color color)
|
||||
{
|
||||
return Color.FromArgb(color.A, 255 - color.R, 255 - color.G, 255 - color.B);
|
||||
}
|
||||
|
||||
public static Color GreyScaled(this Color color)
|
||||
{
|
||||
int greyScaleValue = (color.R + color.G + color.B) / 3;
|
||||
return Color.FromArgb(color.A, greyScaleValue, greyScaleValue, greyScaleValue);
|
||||
}
|
||||
|
||||
public static int ToBGR(this Color color)
|
||||
{
|
||||
return color.B << 16 | color.G << 8 | color.R;
|
||||
}
|
||||
|
||||
public static byte BlendValues(byte source, byte overlay, BlendMode blendType)
|
||||
{
|
||||
return (byte)MathExtensions.Clamp(BlendValues(source / 255f, overlay / 255f, blendType) * 255, 0, 255);
|
||||
}
|
||||
|
||||
public static float BlendValues(float source, float overlay, BlendMode blendType)
|
||||
{
|
||||
source = MathExtensions.Clamp(source, 0.0f, 1.0f);
|
||||
overlay = MathExtensions.Clamp(overlay, 0.0f, 1.0f);
|
||||
float resultValue = blendType switch
|
||||
{
|
||||
BlendMode.Add => source + overlay,
|
||||
BlendMode.Subtract => source - overlay,
|
||||
BlendMode.Multiply => source * overlay,
|
||||
BlendMode.Average => (source + overlay) / 2.0f,
|
||||
BlendMode.AscendingOrder => source > overlay ? overlay : source,
|
||||
BlendMode.DescendingOrder => source < overlay ? overlay : source,
|
||||
BlendMode.Screen => 1f - (1f - source) * (1f - overlay),
|
||||
BlendMode.Overlay => source < 0.5f ? 2f * source * overlay : 1f - 2f * (1f - source) * (1f - overlay),
|
||||
_ => 0.0f
|
||||
};
|
||||
return MathExtensions.Clamp(resultValue, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public static byte Mix(double ratio, byte val1, byte val2)
|
||||
{
|
||||
ratio = MathExtensions.Clamp(ratio, 0.0, 1.0);
|
||||
return (byte)(ratio * val1 + (1.0 - ratio) * val2);
|
||||
}
|
||||
|
||||
public static Color Mix(this Color c1, Color c2, double ratio)
|
||||
{
|
||||
ratio = MathExtensions.Clamp(ratio, 0.0, 1.0);
|
||||
return Color.FromArgb(c1.A,
|
||||
Mix(ratio, c1.R, c2.R),
|
||||
Mix(ratio, c1.G, c2.G),
|
||||
Mix(ratio, c1.B, c2.B)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
PckStudio.Core/Extensions/CursorExtensions.cs
Normal file
51
PckStudio.Core/Extensions/CursorExtensions.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class CursorExtensions
|
||||
{
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct PointStruct
|
||||
{
|
||||
public Int32 x;
|
||||
public Int32 y;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct CursorInfoStruct
|
||||
{
|
||||
/// <summary> The structure size in bytes that must be set via calling Marshal.SizeOf(typeof(CursorInfoStruct)).</summary>
|
||||
public Int32 cbSize;
|
||||
/// <summary> The cursor state: 0 == hidden, 1 == showing, 2 == suppressed (is supposed to be when finger touch is used, but in practice finger touch results in 0, not 2)</summary>
|
||||
public Int32 flags;
|
||||
/// <summary> A handle to the cursor. </summary>
|
||||
public IntPtr hCursor;
|
||||
/// <summary> The cursor screen coordinates.</summary>
|
||||
public PointStruct pt;
|
||||
}
|
||||
|
||||
/// <summary> Must initialize cbSize</summary>
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool GetCursorInfo(ref CursorInfoStruct pci);
|
||||
|
||||
public static bool IsVisible(this Cursor _)
|
||||
{
|
||||
CursorInfoStruct pci = new CursorInfoStruct();
|
||||
pci.cbSize = Marshal.SizeOf(typeof(CursorInfoStruct));
|
||||
GetCursorInfo(ref pci);
|
||||
// const Int32 hidden = 0x00;
|
||||
const Int32 showing = 0x01;
|
||||
// const Int32 suppressed = 0x02;
|
||||
bool isVisible = ((pci.flags & showing) != 0);
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
38
PckStudio.Core/Extensions/EnumerableExtensions.cs
Normal file
38
PckStudio.Core/Extensions/EnumerableExtensions.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
public static IEnumerable<(int index, T value)>enumerate<T>(this IEnumerable<T> array)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (T item in array)
|
||||
{
|
||||
yield return (i++, item);
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
public static bool EqualsAny<T>(this T type, params T[] items)
|
||||
{
|
||||
foreach (T item in items)
|
||||
{
|
||||
if (item.Equals(type))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ContainsAny<T>(this IEnumerable<T> array, params T[] items)
|
||||
{
|
||||
foreach (T item in array)
|
||||
{
|
||||
if (items.Contains(item))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
PckStudio.Core/Extensions/GraphicsExtensions.cs
Normal file
44
PckStudio.Core/Extensions/GraphicsExtensions.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public struct GraphicsConfig
|
||||
{
|
||||
public GraphicsConfig()
|
||||
{
|
||||
CompositingQuality = default;
|
||||
InterpolationMode = default;
|
||||
SmoothingMode = default;
|
||||
PixelOffsetMode = default;
|
||||
CompositingMode = default;
|
||||
}
|
||||
|
||||
public CompositingMode CompositingMode { get; set; }
|
||||
public CompositingQuality CompositingQuality { get; set; }
|
||||
public InterpolationMode InterpolationMode { get; set; }
|
||||
public SmoothingMode SmoothingMode { get; set; }
|
||||
public PixelOffsetMode PixelOffsetMode { get; set; }
|
||||
}
|
||||
|
||||
public static class GraphicsExtensions
|
||||
{
|
||||
public static void ApplyConfig(this Graphics graphics, GraphicsConfig config)
|
||||
{
|
||||
graphics.CompositingMode = config.CompositingMode;
|
||||
graphics.CompositingQuality = config.CompositingQuality;
|
||||
graphics.InterpolationMode = config.InterpolationMode;
|
||||
graphics.SmoothingMode = config.SmoothingMode;
|
||||
graphics.PixelOffsetMode = config.PixelOffsetMode;
|
||||
}
|
||||
|
||||
public static Graphics Fill(this Graphics graphics, Rectangle area, Color color)
|
||||
{
|
||||
Region clip = graphics.Clip;
|
||||
graphics.SetClip(area, CombineMode.Replace);
|
||||
graphics.Clear(color);
|
||||
graphics.SetClip(clip, CombineMode.Replace);
|
||||
return graphics;
|
||||
}
|
||||
}
|
||||
}
|
||||
273
PckStudio.Core/Extensions/ImageExtensions.cs
Normal file
273
PckStudio.Core/Extensions/ImageExtensions.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
/* Copyright (c) 2023-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class ImageExtensions
|
||||
{
|
||||
public static Image ReleaseFromFile(this Image image)
|
||||
{
|
||||
Image img = new Bitmap(image);
|
||||
image.Dispose();
|
||||
return img;
|
||||
}
|
||||
|
||||
public static Image GetArea(this Image source, Rectangle area)
|
||||
{
|
||||
Image tileImage = new Bitmap(area.Width, area.Height);
|
||||
using (Graphics gfx = Graphics.FromImage(tileImage))
|
||||
{
|
||||
gfx.SmoothingMode = SmoothingMode.None;
|
||||
gfx.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||||
gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
gfx.DrawImage(source, new Rectangle(Point.Empty, area.Size), area, GraphicsUnit.Pixel);
|
||||
}
|
||||
return tileImage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an IEnumerable by reading in horizontal order
|
||||
/// </summary>
|
||||
/// <param name="source">this image</param>
|
||||
/// <param name="scalar">Indecates width and height of image sub section</param>
|
||||
/// <returns><see cref="IEnumerable{Image}"/> of type <see cref="Image"/></returns>
|
||||
public static IEnumerable<Image> SplitHorizontal(this Image source, int scalar)
|
||||
{
|
||||
return source.Split(scalar, ImageLayoutDirection.Horizontal);
|
||||
}
|
||||
|
||||
public static IEnumerable<Image> Split(this Image source, int scalar, ImageLayoutDirection layoutDirection)
|
||||
{
|
||||
return Split(source, new Size(scalar, scalar), layoutDirection);
|
||||
}
|
||||
|
||||
public static IEnumerable<Image> Split(this Image source, Size size, ImageLayoutDirection imageLayout)
|
||||
{
|
||||
int rowCount = source.Width / size.Width;
|
||||
int columnCount = source.Height / size.Height;
|
||||
Debug.WriteLine($"Image size: {source.Size}, Area size: {size}, col num: {columnCount}, row num: {rowCount}");
|
||||
for (int i = 0; i < columnCount * rowCount; i++)
|
||||
{
|
||||
int row = Math.DivRem(i, rowCount, out int column);
|
||||
if (imageLayout == ImageLayoutDirection.Vertical)
|
||||
column = Math.DivRem(i, columnCount, out row);
|
||||
Rectangle tileArea = new Rectangle(new Point(column * size.Width, row * size.Height), size);
|
||||
yield return source.GetArea(tileArea);
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
public static IEnumerable<Image> Split(this Image source, ImageLayoutDirection layoutDirection)
|
||||
{
|
||||
for (int i = 0; i < source.Height / source.Width; i++)
|
||||
{
|
||||
ImageSection locationInfo = new ImageSection(source.Size, i, layoutDirection);
|
||||
yield return source.GetArea(locationInfo.Area);
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
public static Image Combine(this IEnumerable<Image> sources, ImageLayoutDirection layoutDirection)
|
||||
{
|
||||
Size imageSize = CalculateImageSize(sources, layoutDirection);
|
||||
var image = new Bitmap(imageSize.Width, imageSize.Height);
|
||||
|
||||
using (var graphic = Graphics.FromImage(image))
|
||||
{
|
||||
foreach ((int i, Image texture) in sources.enumerate())
|
||||
{
|
||||
var info = new ImageSection(texture.Size, i, layoutDirection);
|
||||
graphic.DrawImage(texture, info.Point);
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private static Size CalculateImageSize(IEnumerable<Image> sources, ImageLayoutDirection layoutDirection)
|
||||
{
|
||||
Size size = sources.First().Size;
|
||||
int count = sources.Count();
|
||||
|
||||
if (count < 2)
|
||||
return count < 1 ? Size.Empty : size;
|
||||
|
||||
var horizontal = layoutDirection == ImageLayoutDirection.Horizontal;
|
||||
|
||||
if (!sources.All(img => img.Size == size))
|
||||
throw new InvalidOperationException("Images must have the same width and height.");
|
||||
|
||||
if (horizontal)
|
||||
size.Width *= count;
|
||||
else
|
||||
size.Height *= count;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public static Image Resize(this Image image, Size size, GraphicsConfig graphicsConfig)
|
||||
{
|
||||
return image.Resize(size.Width, size.Height, graphicsConfig);
|
||||
}
|
||||
|
||||
public static Image Resize(this Image image, int width, int height, GraphicsConfig graphicsConfig)
|
||||
{
|
||||
var destRect = new Rectangle(0, 0, width, height);
|
||||
var destImage = new Bitmap(width, height);
|
||||
|
||||
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
|
||||
|
||||
using (var graphics = Graphics.FromImage(destImage))
|
||||
{
|
||||
graphics.ApplyConfig(graphicsConfig);
|
||||
using (var wrapMode = new ImageAttributes())
|
||||
{
|
||||
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
|
||||
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
|
||||
}
|
||||
}
|
||||
return destImage;
|
||||
}
|
||||
|
||||
public static Image Blend(this Image image, Color overlayColor, BlendMode mode)
|
||||
{
|
||||
if (image is not Bitmap baseImage)
|
||||
return image;
|
||||
|
||||
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb);
|
||||
|
||||
BitmapData baseImageData = baseImage.LockBits(new Rectangle(Point.Empty, baseImage.Size),
|
||||
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
|
||||
BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(Point.Empty, bitmapResult.Size),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
|
||||
Parallel.For(0, baseImageData.Stride * baseImageData.Height / 4, (i) =>
|
||||
{
|
||||
int k = i * 4;
|
||||
unsafe
|
||||
{
|
||||
int color = Unsafe.Read<int>((baseImageData.Scan0 + k).ToPointer());
|
||||
byte a = (byte)(color >> 24 & 0xff);
|
||||
if (a == 0)
|
||||
{
|
||||
Unsafe.Write((resultImageData.Scan0 + k).ToPointer(), 0);
|
||||
return;
|
||||
}
|
||||
var b = ColorExtensions.BlendValues((byte)(color >> 0 & 0xff), overlayColor.B, mode);
|
||||
var g = ColorExtensions.BlendValues((byte)(color >> 8 & 0xff), overlayColor.G, mode);
|
||||
var r = ColorExtensions.BlendValues((byte)(color >> 16 & 0xff), overlayColor.R, mode);
|
||||
int blendedValue = a << 24 | r << 16 | g << 8 | b;
|
||||
Unsafe.Write((resultImageData.Scan0 + k).ToPointer(), blendedValue);
|
||||
}
|
||||
});
|
||||
|
||||
bitmapResult.UnlockBits(resultImageData);
|
||||
baseImage.UnlockBits(baseImageData);
|
||||
return bitmapResult;
|
||||
}
|
||||
|
||||
public static Image Blend(this Image image, Image overlay, BlendMode mode)
|
||||
{
|
||||
if (image is not Bitmap baseImage || overlay is not Bitmap overlayImage ||
|
||||
image.Width != overlay.Width || image.Height != overlay.Height)
|
||||
return image;
|
||||
|
||||
BitmapData baseImageData = baseImage.LockBits(new Rectangle(Point.Empty, baseImage.Size),
|
||||
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
|
||||
byte[] baseImageBuffer = new byte[baseImageData.Stride * baseImageData.Height];
|
||||
|
||||
Marshal.Copy(baseImageData.Scan0, baseImageBuffer, 0, baseImageBuffer.Length);
|
||||
|
||||
BitmapData overlayImageData = overlayImage.LockBits(new Rectangle(Point.Empty, overlayImage.Size),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
byte[] overlayImageBuffer = new byte[overlayImageData.Stride * overlayImageData.Height];
|
||||
|
||||
Marshal.Copy(overlayImageData.Scan0, overlayImageBuffer, 0, overlayImageBuffer.Length);
|
||||
|
||||
for (int k = 0; k < baseImageBuffer.Length && k < overlayImageBuffer.Length; k += 4)
|
||||
{
|
||||
baseImageBuffer[k + 0] = ColorExtensions.BlendValues(baseImageBuffer[k + 0], overlayImageBuffer[k + 0], mode);
|
||||
baseImageBuffer[k + 1] = ColorExtensions.BlendValues(baseImageBuffer[k + 1], overlayImageBuffer[k + 1], mode);
|
||||
baseImageBuffer[k + 2] = ColorExtensions.BlendValues(baseImageBuffer[k + 2], overlayImageBuffer[k + 2], mode);
|
||||
}
|
||||
|
||||
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb);
|
||||
BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(Point.Empty, bitmapResult.Size),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
|
||||
Marshal.Copy(baseImageBuffer, 0, resultImageData.Scan0, baseImageBuffer.Length);
|
||||
|
||||
bitmapResult.UnlockBits(resultImageData);
|
||||
baseImage.UnlockBits(baseImageData);
|
||||
overlayImage.UnlockBits(overlayImageData);
|
||||
return bitmapResult;
|
||||
}
|
||||
|
||||
public static Image Interpolate(this Image image1, Image image2, double delta)
|
||||
{
|
||||
delta = MathExtensions.Clamp(delta, 0.0, 1.0);
|
||||
if (image1 is not Bitmap baseImage || image2 is not Bitmap overlayImage ||
|
||||
image1.Width != image2.Width || image1.Height != image2.Height)
|
||||
return image1;
|
||||
|
||||
BitmapData baseImageData = baseImage.LockBits(new Rectangle(Point.Empty, baseImage.Size),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
byte[] baseImageBuffer = new byte[baseImageData.Stride * baseImageData.Height];
|
||||
|
||||
Marshal.Copy(baseImageData.Scan0, baseImageBuffer, 0, baseImageBuffer.Length);
|
||||
|
||||
baseImage.UnlockBits(baseImageData);
|
||||
|
||||
BitmapData overlayImageData = overlayImage.LockBits(new Rectangle(Point.Empty, overlayImage.Size),
|
||||
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
|
||||
byte[] overlayImageBuffer = new byte[overlayImageData.Stride * overlayImageData.Height];
|
||||
|
||||
Marshal.Copy(overlayImageData.Scan0, overlayImageBuffer, 0, overlayImageBuffer.Length);
|
||||
|
||||
overlayImage.UnlockBits(overlayImageData);
|
||||
|
||||
byte[] finalBuffer = new byte[baseImageData.Stride * baseImageData.Height];
|
||||
for (int k = 0; k < baseImageBuffer.Length && k < overlayImageBuffer.Length; k += 4)
|
||||
{
|
||||
finalBuffer[k + 0] = ColorExtensions.Mix(delta, baseImageBuffer[k + 0], overlayImageBuffer[k + 0]);
|
||||
finalBuffer[k + 1] = ColorExtensions.Mix(delta, baseImageBuffer[k + 1], overlayImageBuffer[k + 1]);
|
||||
finalBuffer[k + 2] = ColorExtensions.Mix(delta, baseImageBuffer[k + 2], overlayImageBuffer[k + 2]);
|
||||
finalBuffer[k + 3] = ColorExtensions.Mix(delta, baseImageBuffer[k + 3], overlayImageBuffer[k + 3]);
|
||||
}
|
||||
|
||||
Bitmap bitmapResult = new Bitmap(baseImage.Width, baseImage.Height, PixelFormat.Format32bppArgb);
|
||||
BitmapData resultImageData = bitmapResult.LockBits(new Rectangle(Point.Empty, bitmapResult.Size),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
|
||||
Marshal.Copy(finalBuffer, 0, resultImageData.Scan0, finalBuffer.Length);
|
||||
|
||||
bitmapResult.UnlockBits(resultImageData);
|
||||
return bitmapResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
PckStudio.Core/Extensions/ImageLayoutDirection.cs
Normal file
8
PckStudio.Core/Extensions/ImageLayoutDirection.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public enum ImageLayoutDirection
|
||||
{
|
||||
Horizontal,
|
||||
Vertical
|
||||
}
|
||||
}
|
||||
37
PckStudio.Core/Extensions/ImageSection.cs
Normal file
37
PckStudio.Core/Extensions/ImageSection.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
internal struct ImageSection
|
||||
{
|
||||
public readonly Size Size;
|
||||
public readonly Point Point;
|
||||
public readonly Rectangle Area;
|
||||
|
||||
internal ImageSection(Size originalSize, int index, ImageLayoutDirection layoutDirection)
|
||||
{
|
||||
switch(layoutDirection)
|
||||
{
|
||||
case ImageLayoutDirection.Horizontal:
|
||||
{
|
||||
Size = new Size(originalSize.Height, originalSize.Height);
|
||||
Point = new Point(index * originalSize.Height, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case ImageLayoutDirection.Vertical:
|
||||
{
|
||||
Size = new Size(originalSize.Width, originalSize.Width);
|
||||
Point = new Point(0, index * originalSize.Width);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Size = Size.Empty;
|
||||
Point = new Point(-1, -1);
|
||||
break;
|
||||
}
|
||||
Area = new Rectangle(Point, Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
PckStudio.Core/Extensions/ListExtensions.cs
Normal file
20
PckStudio.Core/Extensions/ListExtensions.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class ListExtensions
|
||||
{
|
||||
public static IList<T> Swap<T>(this IList<T> list, int index1, int index2)
|
||||
{
|
||||
T temp = list[index1];
|
||||
list[index1] = list[index2];
|
||||
list[index2] = temp;
|
||||
return list;
|
||||
}
|
||||
|
||||
public static bool IndexInRange<T>(this IList<T> list, int index)
|
||||
{
|
||||
return index >= 0 && index < list.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
PckStudio.Core/Extensions/LocFileExtensions.cs
Normal file
17
PckStudio.Core/Extensions/LocFileExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using OMI.Formats.Languages;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class LocFileExtensions
|
||||
{
|
||||
|
||||
public static void InitializeDefault(this LOCFile locFile, string packName) => locFile.Initialize("en-EN", ("IDS_DISPLAY_NAME", packName));
|
||||
|
||||
public static void Initialize(this LOCFile locFile, string language, params (string, string)[] locKeyValuePairs)
|
||||
{
|
||||
locFile.AddLanguage(language);
|
||||
foreach ((string, string) locKeyValue in locKeyValuePairs)
|
||||
locFile.AddLocKey(locKeyValue.Item1, locKeyValue.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
PckStudio.Core/Extensions/MaterialContainerExtensions.cs
Normal file
37
PckStudio.Core/Extensions/MaterialContainerExtensions.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using OMI.Formats.Material;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class MaterialContainerExtensions
|
||||
{
|
||||
private static readonly MaterialContainer.Material[] defaultMaterials = [
|
||||
new MaterialContainer.Material("bat", "entity_alphatest"),
|
||||
new MaterialContainer.Material("ender_dragon", "entity_emissive_alpha"),
|
||||
new MaterialContainer.Material("blaze_head", "entity_emissive_alpha"),
|
||||
new MaterialContainer.Material("drowned", "entity_emissive_alpha"),
|
||||
new MaterialContainer.Material("enderman", "entity_emissive_alpha"),
|
||||
new MaterialContainer.Material("enderman_invisible", "entity_emissive_alpha_only"),
|
||||
new MaterialContainer.Material("ghast", "entity_emissive_alpha"),
|
||||
new MaterialContainer.Material("guardian", "entity_alphatest"),
|
||||
new MaterialContainer.Material("magma_cube", "entity_emissive_alpha"),
|
||||
new MaterialContainer.Material("zombie_pigman", "entity"),
|
||||
new MaterialContainer.Material("phantom", "entity_emissive_alpha"),
|
||||
new MaterialContainer.Material("phantom_invisible", "entity_emissive_alpha_only"),
|
||||
new MaterialContainer.Material("sheep", "entity_change_color"),
|
||||
new MaterialContainer.Material("shulker", "entity_change_color"),
|
||||
new MaterialContainer.Material("skeleton", "entity_alphatest"),
|
||||
new MaterialContainer.Material("spider", "entity_emissive_alpha"),
|
||||
new MaterialContainer.Material("spider_invisible", "entity_emissive_alpha_only"),
|
||||
new MaterialContainer.Material("stray", "entity_alphatest"),
|
||||
new MaterialContainer.Material("iron_golem", "entity_alphatest"),
|
||||
new MaterialContainer.Material("wither_boss", "entity_alphatest"),
|
||||
new MaterialContainer.Material("wither_skeleton", "entity_alphatest"),
|
||||
new MaterialContainer.Material("wolf", "entity_alphatest_change_color")
|
||||
];
|
||||
|
||||
public static void InitializeDefault(this MaterialContainer materials)
|
||||
{
|
||||
materials.AddRange(defaultMaterials);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
PckStudio.Core/Extensions/MaterialExtensions.cs
Normal file
17
PckStudio.Core/Extensions/MaterialExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OMI.Formats.Material;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class MaterialExtensions
|
||||
{
|
||||
public static bool HasInvalidEntries(this MaterialContainer materials)
|
||||
{
|
||||
return materials.Any(mat => !MaterialContainer.SupportedEntities.Contains(mat.Name) || !MaterialContainer.ValidMaterialTypes.Contains(mat.Type));
|
||||
}
|
||||
}
|
||||
}
|
||||
16
PckStudio.Core/Extensions/MathExtensions.cs
Normal file
16
PckStudio.Core/Extensions/MathExtensions.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public class MathExtensions
|
||||
{
|
||||
public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
|
||||
{
|
||||
if (value.CompareTo(min) < 0)
|
||||
return min;
|
||||
if (value.CompareTo(max) > 0)
|
||||
return max;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
PckStudio.Core/Extensions/ModelBoxExtension.cs
Normal file
20
PckStudio.Core/Extensions/ModelBoxExtension.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using OMI.Formats.Model;
|
||||
using System.Numerics;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class ModelBoxExtension
|
||||
{
|
||||
public static BoundingBox GetBoundingBox(this ModelBox modelBox)
|
||||
{
|
||||
Vector3 halfSize = modelBox.Size / 2f;
|
||||
Vector3 halfSizeInflated = new Vector3(modelBox.Inflate) + halfSize;
|
||||
Vector3 transformedCenter = modelBox.Position + halfSize;
|
||||
Vector3 start = transformedCenter - halfSizeInflated;
|
||||
Vector3 end = transformedCenter + halfSizeInflated;
|
||||
return new BoundingBox(start, end);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
25
PckStudio.Core/Extensions/OpenTKExtensions.cs
Normal file
25
PckStudio.Core/Extensions/OpenTKExtensions.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTK;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class OpenTKExtensions
|
||||
{
|
||||
public static Matrix4 Pivoted(this Matrix4 rotation, Vector3 pivot)
|
||||
{
|
||||
var model = Matrix4.CreateTranslation(pivot);
|
||||
model *= rotation;
|
||||
model *= Matrix4.CreateTranslation(pivot).Inverted();
|
||||
return model;
|
||||
}
|
||||
|
||||
public static Vector3 Abs(Vector3 value)
|
||||
{
|
||||
return new Vector3(Math.Abs(value.X), Math.Abs(value.Y), Math.Abs(value.Z));
|
||||
}
|
||||
}
|
||||
}
|
||||
226
PckStudio.Core/Extensions/PckAssetExtensions.cs
Normal file
226
PckStudio.Core/Extensions/PckAssetExtensions.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Drawing;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using OMI.Formats.Languages;
|
||||
using OMI.Formats.Pck;
|
||||
using OMI.Workers;
|
||||
|
||||
using PckStudio.Interfaces;
|
||||
using PckStudio.Core.Deserializer;
|
||||
using PckStudio.Core.Serializer;
|
||||
using PckStudio.Core.Skin;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class PckAssetExtensions
|
||||
{
|
||||
private const string MipMap = "MipMapLevel";
|
||||
|
||||
public static PckAsset CreateNewAssetIf(this PckFile pck, bool condition, string filename, PckAssetType filetype, IDataFormatWriter writer)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
return pck.CreateNewAsset(filename, filetype, writer);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public static PckAsset CreateNewAsset(this PckFile pck, string filename, PckAssetType filetype, IDataFormatWriter writer)
|
||||
{
|
||||
PckAsset asset = pck.CreateNewAsset(filename, filetype);
|
||||
asset.SetData(writer);
|
||||
return asset;
|
||||
}
|
||||
|
||||
public static Image GetTexture(this PckAsset asset)
|
||||
{
|
||||
if (asset.Type != PckAssetType.SkinFile &&
|
||||
asset.Type != PckAssetType.CapeFile &&
|
||||
asset.Type != PckAssetType.TextureFile)
|
||||
{
|
||||
throw new Exception("Asset is not suitable to contain image data.");
|
||||
}
|
||||
return asset.GetDeserializedData(ImageDeserializer.DefaultDeserializer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the skin id of the skin <paramref name="asset"/>
|
||||
/// </summary>
|
||||
/// <param name="asset"></param>
|
||||
/// <returns>Non-zero base number on success, otherwise 0</returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public static int GetSkinId(this PckAsset asset)
|
||||
{
|
||||
if (asset.Type != PckAssetType.SkinFile)
|
||||
throw new InvalidOperationException("Asset is not a skin.");
|
||||
|
||||
const string skinAssetnamePrefix = "dlcskin";
|
||||
|
||||
string assetPath = Path.GetFileNameWithoutExtension(asset.Filename);
|
||||
if (!assetPath.StartsWith(skinAssetnamePrefix))
|
||||
{
|
||||
Trace.TraceWarning($"[{nameof(GetSkinId)}] Asset name does not start with '{skinAssetnamePrefix}'");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int skinId = 0;
|
||||
if (!int.TryParse(assetPath.Substring(skinAssetnamePrefix.Length), out skinId))
|
||||
{
|
||||
Trace.TraceWarning($"[{nameof(GetSkinId)}] Failed to parse Skin Id");
|
||||
}
|
||||
return skinId;
|
||||
}
|
||||
|
||||
public static Skin.Skin GetSkin(this PckAsset asset)
|
||||
{
|
||||
if (asset.Type != PckAssetType.SkinFile)
|
||||
throw new InvalidOperationException("Asset is not a skin.");
|
||||
|
||||
int skinId = asset.GetSkinId();
|
||||
|
||||
string name = asset.GetProperty("DISPLAYNAME");
|
||||
Image texture = asset.GetTexture();
|
||||
SkinANIM anim = asset.GetProperty("ANIM", SkinANIM.FromString);
|
||||
IEnumerable<SkinBOX> boxes = asset.GetMultipleProperties("BOX").Select(kv => SkinBOX.FromString(kv.Value));
|
||||
IEnumerable<SkinPartOffset> offsets = asset.GetMultipleProperties("OFFSET").Select(kv => SkinPartOffset.FromString(kv.Value));
|
||||
return new Skin.Skin(name, skinId, texture, anim, boxes, offsets);
|
||||
}
|
||||
|
||||
public static void SetSkin(this PckAsset asset, Skin.Skin skin, LOCFile localizationFile)
|
||||
{
|
||||
if (asset.Type != PckAssetType.SkinFile)
|
||||
throw new InvalidOperationException("Asset is not a skin file");
|
||||
|
||||
asset.SetTexture(skin.Texture);
|
||||
|
||||
string skinId = skin.Identifier.ToString("d08");
|
||||
|
||||
// TODO: keep filepath
|
||||
asset.Filename = $"dlcskin{skinId}.png";
|
||||
|
||||
asset.SetProperty("DISPLAYNAME", skin.MetaData.Name);
|
||||
|
||||
if (localizationFile is not null)
|
||||
{
|
||||
string skinLocKey = $"IDS_dlcskin{skinId}_DISPLAYNAME";
|
||||
asset.SetProperty("DISPLAYNAMEID", skinLocKey);
|
||||
localizationFile.SetLocEntry(skinLocKey, skin.MetaData.Name);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(skin.MetaData.Theme))
|
||||
{
|
||||
asset.SetProperty("THEMENAME", skin.MetaData.Theme);
|
||||
|
||||
if (localizationFile is not null)
|
||||
{
|
||||
string skinThemeLocKey = $"IDS_dlcskin{skinId}_THEMENAME";
|
||||
asset.SetProperty("THEMENAMEID", skinThemeLocKey);
|
||||
localizationFile.SetLocEntry(skinThemeLocKey, skin.MetaData.Theme);
|
||||
}
|
||||
}
|
||||
|
||||
if (skin.HasCape)
|
||||
{
|
||||
asset.SetProperty("CAPEPATH", $"dlccape{skinId}.png");
|
||||
}
|
||||
|
||||
asset.SetProperty("ANIM", skin.Anim.ToString());
|
||||
asset.SetProperty("GAME_FLAGS", "0x18");
|
||||
asset.SetProperty("FREE", "1");
|
||||
|
||||
asset.RemoveProperties("BOX");
|
||||
asset.RemoveProperties("OFFSET");
|
||||
|
||||
foreach (SkinBOX box in skin.Model.AdditionalBoxes)
|
||||
{
|
||||
asset.AddProperty(box.ToProperty());
|
||||
}
|
||||
foreach (SkinPartOffset offset in skin.Model.PartOffsets)
|
||||
{
|
||||
asset.AddProperty(offset.ToProperty());
|
||||
}
|
||||
}
|
||||
|
||||
public static T GetDeserializedData<T>(this PckAsset asset, IPckAssetDeserializer<T> deserializer)
|
||||
{
|
||||
return deserializer.Deserialize(asset);
|
||||
}
|
||||
|
||||
public static T GetData<T>(this PckAsset asset, IDataFormatReader<T> formatReader) where T : class
|
||||
{
|
||||
using var ms = new MemoryStream(asset.Data);
|
||||
return formatReader.FromStream(ms);
|
||||
}
|
||||
|
||||
public static void SetSerializedData<T>(this PckAsset asset, T obj, IPckAssetSerializer<T> serializer)
|
||||
{
|
||||
serializer.Serialize(obj, ref asset);
|
||||
}
|
||||
|
||||
public static void SetData(this PckAsset asset, IDataFormatWriter formatWriter)
|
||||
{
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
formatWriter.WriteToStream(stream);
|
||||
asset.SetData(stream.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetTexture(this PckAsset asset, Image image)
|
||||
{
|
||||
if (asset.Type != PckAssetType.SkinFile &&
|
||||
asset.Type != PckAssetType.CapeFile &&
|
||||
asset.Type != PckAssetType.TextureFile)
|
||||
{
|
||||
throw new Exception("Asset is not suitable to contain image data.");
|
||||
}
|
||||
asset.SetSerializedData(image, ImageSerializer.DefaultSerializer);
|
||||
}
|
||||
|
||||
public static bool IsMipmappedFile(this PckAsset asset)
|
||||
{
|
||||
// We only want to test the file name itself. ex: "terrainMipMapLevel2"
|
||||
string name = Path.GetFileNameWithoutExtension(asset.Filename);
|
||||
|
||||
// check if last character is a digit (0-9). If not return false
|
||||
if (!char.IsDigit(name[name.Length - 1]))
|
||||
return false;
|
||||
|
||||
// If string does not end with MipMapLevel, then it's not MipMapped
|
||||
if (!name.Remove(name.Length - 1, 1).EndsWith(MipMap))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetNormalPath(this PckAsset asset)
|
||||
{
|
||||
if (!asset.IsMipmappedFile())
|
||||
return asset.Filename;
|
||||
string ext = Path.GetExtension(asset.Filename);
|
||||
return asset.Filename.Remove(asset.Filename.Length - (MipMap.Length + 1) - ext.Length) + ext;
|
||||
}
|
||||
|
||||
public static void DeserializeProperties(this PckAsset asset, IEnumerable<string> serializedData)
|
||||
{
|
||||
IEnumerable<KeyValuePair<string, string>> lines = serializedData
|
||||
.Select(line => line.Split([' '], 2))
|
||||
.Where (keyValue => keyValue.Length == 2)
|
||||
.Select(keyValue => new KeyValuePair<string, string>(keyValue[0].Replace(":", ""), keyValue[1]));
|
||||
foreach (KeyValuePair<string, string> kv in lines)
|
||||
{
|
||||
asset.AddProperty(kv);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> SerializeProperties(this PckAsset asset, string seperater = ":")
|
||||
{
|
||||
IReadOnlyList<KeyValuePair<string, string>> properties = asset.GetProperties();
|
||||
return properties.Select(property => property.Key + seperater + property.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
PckStudio.Core/Extensions/PictureBoxExtensions.cs
Normal file
23
PckStudio.Core/Extensions/PictureBoxExtensions.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class PictureBoxExtensions
|
||||
{
|
||||
public static bool IsAnimating(this PictureBox pictureBox)
|
||||
{
|
||||
FieldInfo fi = typeof(PictureBox).GetField("currentlyAnimating", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
return (bool)fi.GetValue(pictureBox);
|
||||
}
|
||||
|
||||
public static void Animate(this PictureBox pictureBox, bool animate)
|
||||
{
|
||||
MethodInfo animateMethod = typeof(PictureBox).GetMethod("Animate", BindingFlags.NonPublic | BindingFlags.Instance,
|
||||
null, new Type[] { typeof(bool) }, null);
|
||||
animateMethod.Invoke(pictureBox, new object[] { animate });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
81
PckStudio.Core/Extensions/SkinBOXExtensions.cs
Normal file
81
PckStudio.Core/Extensions/SkinBOXExtensions.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using PckStudio.Core.Skin;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class SkinBOXExtensions
|
||||
{
|
||||
public static GraphicsPath GetUVGraphicsPath(this SkinBOX skinBOX, Vector2 tillingFactor)
|
||||
{
|
||||
var types = new byte[9];
|
||||
var points = new PointF[9];
|
||||
|
||||
var path = new GraphicsPath(FillMode.Winding);
|
||||
|
||||
Vector2 uv = skinBOX.UV;
|
||||
Vector3 size = skinBOX.Size;
|
||||
|
||||
path.AddRectangle(new RectangleF(new PointF((uv.X ) * tillingFactor.X, (uv.Y + size.Z) * tillingFactor.Y), new SizeF(size.Z * tillingFactor.X, size.Y * tillingFactor.Y)));
|
||||
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z ) * tillingFactor.X, (uv.Y + size.Z) * tillingFactor.Y), new SizeF(size.X * tillingFactor.X, size.Y * tillingFactor.Y)));
|
||||
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z + size.X ) * tillingFactor.X, (uv.Y + size.Z) * tillingFactor.Y), new SizeF(size.Z * tillingFactor.X, size.Y * tillingFactor.Y)));
|
||||
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z * 2 + size.X) * tillingFactor.X, (uv.Y + size.Z) * tillingFactor.Y), new SizeF(size.X * tillingFactor.X, size.Y * tillingFactor.Y)));
|
||||
|
||||
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z ) * tillingFactor.X, (uv.Y ) * tillingFactor.Y), new SizeF(size.X * tillingFactor.X, size.Z * tillingFactor.Y)));
|
||||
path.AddRectangle(new RectangleF(new PointF((uv.X + size.Z + size.X ) * tillingFactor.X, (uv.Y ) * tillingFactor.Y), new SizeF(size.X * tillingFactor.X, size.Z * tillingFactor.Y)));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static GraphicsPath GetUVGraphicsPath(this SkinBOX skinBox)
|
||||
{
|
||||
return skinBox.GetUVGraphicsPath(Vector2.One);
|
||||
}
|
||||
|
||||
public static string GetOverlayType(this SkinBOX skinBox)
|
||||
{
|
||||
if (!skinBox.IsValidType())
|
||||
return "";
|
||||
if (skinBox.IsOverlayPart())
|
||||
return skinBox.Type;
|
||||
int index = Array.IndexOf(SkinBOX.BaseTypes, skinBox.Type);
|
||||
return SkinBOX.OverlayTypes.IndexInRange(index) ? SkinBOX.OverlayTypes[index] : "";
|
||||
}
|
||||
|
||||
public static string GetOverlayType(string type)
|
||||
{
|
||||
if (!SkinBOX.IsValidType(type))
|
||||
return "";
|
||||
if (SkinBOX.IsOverlayPart(type))
|
||||
return type;
|
||||
int index = Array.IndexOf(SkinBOX.BaseTypes, type);
|
||||
return SkinBOX.OverlayTypes.IndexInRange(index) ? SkinBOX.OverlayTypes[index] : "";
|
||||
}
|
||||
|
||||
public static string GetBaseType(this SkinBOX skinBox)
|
||||
{
|
||||
if (!skinBox.IsValidType())
|
||||
return "";
|
||||
if (skinBox.IsBasePart())
|
||||
return skinBox.Type;
|
||||
int index = Array.IndexOf(SkinBOX.OverlayTypes, skinBox.Type);
|
||||
return SkinBOX.BaseTypes.IndexInRange(index) ? SkinBOX.BaseTypes[index] : "";
|
||||
}
|
||||
|
||||
public static string GetBaseType(string type)
|
||||
{
|
||||
if (!SkinBOX.IsValidType(type))
|
||||
return "";
|
||||
if (SkinBOX.IsBasePart(type))
|
||||
return type;
|
||||
int index = Array.IndexOf(SkinBOX.OverlayTypes, type);
|
||||
return SkinBOX.BaseTypes.IndexInRange(index) ? SkinBOX.BaseTypes[index] : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
71
PckStudio.Core/Extensions/SkinExtensions.cs
Normal file
71
PckStudio.Core/Extensions/SkinExtensions.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OMI.Formats.Languages;
|
||||
using OMI.Formats.Pck;
|
||||
using PckStudio.Core.Skin;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class SkinExtensions
|
||||
{
|
||||
public static PckAsset CreateFile(this Skin.Skin skin, LOCFile localizationFile)
|
||||
{
|
||||
string skinId = skin.Identifier.ToString("d08");
|
||||
PckAsset skinFile = new PckAsset($"dlcskin{skinId}.png", PckAssetType.SkinFile);
|
||||
|
||||
skinFile.AddProperty("DISPLAYNAME", skin.MetaData.Name);
|
||||
if (localizationFile is not null)
|
||||
{
|
||||
string skinLocKey = $"IDS_dlcskin{skinId}_DISPLAYNAME";
|
||||
skinFile.AddProperty("DISPLAYNAMEID", skinLocKey);
|
||||
localizationFile.AddLocKey(skinLocKey, skin.MetaData.Name);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(skin.MetaData.Theme))
|
||||
{
|
||||
skinFile.AddProperty("THEMENAME", skin.MetaData.Theme);
|
||||
if (localizationFile is not null)
|
||||
{
|
||||
skinFile.AddProperty("THEMENAMEID", $"IDS_dlcskin{skinId}_THEMENAME");
|
||||
localizationFile.AddLocKey($"IDS_dlcskin{skinId}_THEMENAME", skin.MetaData.Theme);
|
||||
}
|
||||
}
|
||||
|
||||
if (skin.HasCape)
|
||||
{
|
||||
skinFile.AddProperty("CAPEPATH", $"dlccape{skinId}.png");
|
||||
}
|
||||
|
||||
skinFile.AddProperty("ANIM", skin.Anim);
|
||||
skinFile.AddProperty("GAME_FLAGS", "0x18");
|
||||
skinFile.AddProperty("FREE", "1");
|
||||
|
||||
foreach (SkinBOX box in skin.Model.AdditionalBoxes)
|
||||
{
|
||||
skinFile.AddProperty(box.ToProperty());
|
||||
}
|
||||
foreach (SkinPartOffset offset in skin.Model.PartOffsets)
|
||||
{
|
||||
skinFile.AddProperty(offset.ToProperty());
|
||||
}
|
||||
|
||||
skinFile.SetTexture(skin.Texture);
|
||||
|
||||
return skinFile;
|
||||
}
|
||||
|
||||
public static PckAsset CreateCapeFile(this Skin.Skin skin)
|
||||
{
|
||||
if (!skin.HasCape)
|
||||
throw new InvalidOperationException("Skin does not contain a cape.");
|
||||
string skinId = skin.Identifier.ToString("d08");
|
||||
PckAsset capeFile = new PckAsset($"dlccape{skinId}.png", PckAssetType.CapeFile);
|
||||
capeFile.SetTexture(skin.CapeTexture);
|
||||
return capeFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
PckStudio.Core/Extensions/System.Numerics.cs
Normal file
29
PckStudio.Core/Extensions/System.Numerics.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class NumericsExtensions
|
||||
{
|
||||
//internal static Cube ToCube(this SkinBOX skinBOX) => skinBOX.ToCube(0f);
|
||||
|
||||
//internal static Cube ToCube(this SkinBOX skinBOX, float inflate, bool flipZMapping = false)
|
||||
// => new Cube(skinBOX.Pos.ToOpenTKVector(), skinBOX.Size.ToOpenTKVector(), skinBOX.UV.ToOpenTKVector(), skinBOX.Scale + inflate, skinBOX.Mirror, flipZMapping);
|
||||
public static OpenTK.Vector3 ToOpenTKVector(this System.Numerics.Vector3 vector3)
|
||||
{
|
||||
return new OpenTK.Vector3(vector3.X, vector3.Y, vector3.Z);
|
||||
}
|
||||
|
||||
public static OpenTK.Vector2 ToOpenTKVector(this System.Numerics.Vector2 vector2)
|
||||
{
|
||||
return new OpenTK.Vector2(vector2.X, vector2.Y);
|
||||
}
|
||||
|
||||
public static System.Numerics.Vector3 ToNumericsVector(this OpenTK.Vector3 vector3)
|
||||
{
|
||||
return new System.Numerics.Vector3(vector3.X, vector3.Y, vector3.Z);
|
||||
}
|
||||
|
||||
public static System.Numerics.Vector2 ToNumericsVector(this OpenTK.Vector2 vector2)
|
||||
{
|
||||
return new System.Numerics.Vector2(vector2.X, vector2.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
PckStudio.Core/Extensions/TreeNodeExtensions.cs
Normal file
47
PckStudio.Core/Extensions/TreeNodeExtensions.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class TreeNodeExtensions
|
||||
{
|
||||
public static bool IsTagOfType<T>(this TreeNode node) where T : class
|
||||
{
|
||||
return node?.Tag is T;
|
||||
}
|
||||
|
||||
public static bool TryGetTagData<TOut>(this TreeNode node, out TOut tagData) where TOut : class
|
||||
{
|
||||
if (node?.Tag is TOut data)
|
||||
{
|
||||
tagData = data;
|
||||
return true;
|
||||
}
|
||||
tagData = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Contains(this TreeNode thisNode, TreeNode childNode)
|
||||
{
|
||||
if (childNode.Parent == null)
|
||||
return false;
|
||||
if (thisNode.Equals(childNode.Parent))
|
||||
return true;
|
||||
// If the parent node is not equal to the first node,
|
||||
// call the TreeNode.Contains recursively using the parent of the node.
|
||||
return thisNode.Contains(childNode.Parent);
|
||||
}
|
||||
|
||||
public static List<TreeNode> GetChildNodes(this TreeNode thisNode)
|
||||
{
|
||||
List<TreeNode> nodes = new List<TreeNode>(thisNode.Nodes.Count);
|
||||
foreach (TreeNode node in thisNode.Nodes)
|
||||
{
|
||||
nodes.Add(node);
|
||||
nodes.AddRange(node.GetChildNodes());
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
28
PckStudio.Core/Extensions/TreeViewExtensions.cs
Normal file
28
PckStudio.Core/Extensions/TreeViewExtensions.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace PckStudio.Core.Extensions
|
||||
{
|
||||
public static class TreeViewExtensions
|
||||
{
|
||||
public static TreeNode[] FindPath(this TreeView treeView, string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
return Array.Empty<TreeNode>();
|
||||
|
||||
if (!path.Contains(treeView.PathSeparator))
|
||||
{
|
||||
return treeView.Nodes.Find(path, false);
|
||||
}
|
||||
|
||||
string segment = path.Substring(0, path.IndexOf(treeView.PathSeparator));
|
||||
if (treeView.Nodes.ContainsKey(segment))
|
||||
{
|
||||
TreeNode[] res = treeView.Nodes[segment].GetChildNodes().Where(node => node.FullPath == path).ToArray();
|
||||
return res;
|
||||
}
|
||||
return Array.Empty<TreeNode>();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
PckStudio.Core/FileDialogFilter.cs
Normal file
23
PckStudio.Core/FileDialogFilter.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.IO;
|
||||
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public readonly struct FileDialogFilter
|
||||
{
|
||||
public readonly string Description;
|
||||
public readonly string Pattern;
|
||||
|
||||
public string Extension => Path.GetExtension(Pattern);
|
||||
|
||||
public FileDialogFilter(string description, string pattern)
|
||||
{
|
||||
Description = description;
|
||||
Pattern = pattern;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Description}|{Pattern}";
|
||||
}
|
||||
}
|
||||
}
|
||||
157
PckStudio.Core/FileFormats/PckAudioFile.cs
Normal file
157
PckStudio.Core/FileFormats/PckAudioFile.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OMI.Formats.Languages;
|
||||
|
||||
namespace PckStudio.Core.FileFormats
|
||||
{
|
||||
public class PckAudioFile
|
||||
{
|
||||
public class InvalidCategoryException : Exception
|
||||
{
|
||||
public InvalidCategoryException(string message) : base(message)
|
||||
{ }
|
||||
}
|
||||
|
||||
public readonly int type = 1;
|
||||
|
||||
public AudioCategory[] Categories => Array.FindAll(_categories, c => c is not null);
|
||||
private AudioCategory[] _categories { get; } = new AudioCategory[9];
|
||||
|
||||
public Dictionary<string, string> Credits { get; } = new Dictionary<string, string>();
|
||||
|
||||
public class AudioCategory
|
||||
{
|
||||
public enum EAudioType : int
|
||||
{
|
||||
Overworld,
|
||||
Nether,
|
||||
End,
|
||||
Creative,
|
||||
Menu,
|
||||
Battle,
|
||||
Tumble,
|
||||
Glide,
|
||||
BuildOff,
|
||||
}
|
||||
|
||||
public enum EAudioParameterType : int
|
||||
{
|
||||
unk0, // dimension music
|
||||
unk1, // unused music ?
|
||||
}
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public EAudioType AudioType { get; }
|
||||
public List<string> SongNames { get; } = new List<string>();
|
||||
public EAudioParameterType parameterType { get; }
|
||||
|
||||
public AudioCategory(string name, EAudioParameterType parameterType, EAudioType audioType)
|
||||
{
|
||||
this.Name = name;
|
||||
this.parameterType = parameterType;
|
||||
this.AudioType = audioType;
|
||||
}
|
||||
}
|
||||
|
||||
public string[] GetCredits() => Credits.Values.ToArray();
|
||||
public string GetCreditsString() => string.Join("\n", Credits.Values.ToArray());
|
||||
|
||||
public void AddCredits(params string[] credits)
|
||||
{
|
||||
foreach (var credit in credits)
|
||||
{
|
||||
AddCredit(credit);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies internal Credits to loc file
|
||||
/// </summary>
|
||||
public void ApplyCredits(LOCFile locFile)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> credit in Credits)
|
||||
{
|
||||
locFile.SetLocEntry(credit.Key, credit.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears and sets the new supplied <paramref name="credits"/>
|
||||
/// </summary>
|
||||
public void SetCredits(params string[] credits)
|
||||
{
|
||||
Credits.Clear();
|
||||
AddCredits(credits);
|
||||
}
|
||||
|
||||
public bool SetCredit(string creditId, string s)
|
||||
{
|
||||
if (!Credits.ContainsKey(creditId))
|
||||
return false;
|
||||
Credits[creditId] = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddCredit(string credit)
|
||||
{
|
||||
Credits.Add($"IDS_CREDIT{(Credits.Count > 0 ? $"_{Credits.Count+1}" : string.Empty)}", credit);
|
||||
}
|
||||
|
||||
public void AddCreditId(string creditId) => Credits.Add(creditId, string.Empty);
|
||||
|
||||
|
||||
/// <exception cref="InvalidCategoryException"></exception>
|
||||
public bool HasCategory(AudioCategory.EAudioType category) => GetCategory(category) is AudioCategory;
|
||||
|
||||
/// <exception cref="InvalidCategoryException"></exception>
|
||||
public AudioCategory GetCategory(AudioCategory.EAudioType category)
|
||||
{
|
||||
if (category < AudioCategory.EAudioType.Overworld ||
|
||||
category > AudioCategory.EAudioType.BuildOff)
|
||||
throw new InvalidCategoryException(nameof(category));
|
||||
return _categories[(int)category];
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidCategoryException"></exception>
|
||||
public bool TryGetCategory(AudioCategory.EAudioType category, out AudioCategory audioCategory)
|
||||
{
|
||||
if (GetCategory(category) is AudioCategory a)
|
||||
{
|
||||
audioCategory = a;
|
||||
return true;
|
||||
}
|
||||
audioCategory = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <returns>True when category was created, otherwise false</returns>
|
||||
/// <exception cref="InvalidCategoryException"></exception>
|
||||
public bool AddCategory(AudioCategory.EAudioParameterType parameterType, AudioCategory.EAudioType category, string name = "")
|
||||
{
|
||||
if (category < AudioCategory.EAudioType.Overworld ||
|
||||
category > AudioCategory.EAudioType.BuildOff)
|
||||
throw new InvalidCategoryException(nameof(category));
|
||||
bool exists = HasCategory(category);
|
||||
if (!exists)
|
||||
_categories[(int)category] = new AudioCategory(name, parameterType, category);
|
||||
return !exists;
|
||||
}
|
||||
|
||||
/// <returns>True when category was created, otherwise false</returns>
|
||||
/// <exception cref="InvalidCategoryException"></exception>
|
||||
public bool AddCategory(AudioCategory.EAudioType category)
|
||||
=> AddCategory(AudioCategory.EAudioParameterType.unk0, category);
|
||||
|
||||
/// <returns>True when category was removed, otherwise false</returns>
|
||||
/// <exception cref="InvalidCategoryException"></exception>
|
||||
public bool RemoveCategory(AudioCategory.EAudioType category)
|
||||
{
|
||||
bool exists = HasCategory(category);
|
||||
if (exists)
|
||||
_categories[(int)category] = null;
|
||||
return exists;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
78
PckStudio.Core/GameConstants.cs
Normal file
78
PckStudio.Core/GameConstants.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using PckStudio.Core.Extensions;
|
||||
using PckStudio.Core.Skin;
|
||||
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public static class GameConstants
|
||||
{
|
||||
|
||||
public static readonly Vector3 SkinHeadTranslation = Vector3.Zero;
|
||||
public static readonly Vector3 SkinHeadPivot = Vector3.Zero;
|
||||
|
||||
public static readonly Vector3 SkinBodyTranslation = Vector3.Zero;
|
||||
public static readonly Vector3 SkinBodyPivot = Vector3.Zero;
|
||||
|
||||
public static readonly Vector3 SkinRightArmTranslation = new(-5f, 2f, 0f);
|
||||
public static readonly Vector3 SkinRightArmPivot = new(-6f, 2f, 0f);
|
||||
|
||||
public static readonly Vector3 SkinLeftArmTranslation = new(5f, 2f, 0f);
|
||||
public static readonly Vector3 SkinLeftArmPivot = new(6f, 2f, 0f);
|
||||
|
||||
public static readonly Vector3 SkinRightLegTranslation = new(-2f, 12f, 0f);
|
||||
public static readonly Vector3 SkinRightLegPivot = new(-2f, 12f, 0f);
|
||||
|
||||
public static readonly Vector3 SkinLeftLegTranslation = new(2f, 12f, 0f);
|
||||
public static readonly Vector3 SkinLeftLegPivot = new(2f, 12f, 0f);
|
||||
|
||||
private static Dictionary<string, PositioningInfo> _posisioningInfos = new Dictionary<string, PositioningInfo>()
|
||||
{
|
||||
["HEAD"] = new PositioningInfo(SkinHeadTranslation, SkinHeadPivot),
|
||||
["BODY"] = new PositioningInfo(SkinBodyTranslation, SkinBodyPivot),
|
||||
["ARM0"] = new PositioningInfo(SkinRightArmTranslation, SkinRightArmPivot),
|
||||
["ARM1"] = new PositioningInfo(SkinLeftArmTranslation, SkinLeftArmPivot),
|
||||
["LEG0"] = new PositioningInfo(SkinRightLegTranslation, SkinRightLegPivot),
|
||||
["LEG1"] = new PositioningInfo(SkinLeftLegTranslation, SkinLeftLegPivot),
|
||||
};
|
||||
public record struct PositioningInfo(Vector3 Translation, Vector3 Pivot);
|
||||
|
||||
public static PositioningInfo GetPositioningInfo(string partName)
|
||||
{
|
||||
if (SkinBOX.IsOverlayPart(partName))
|
||||
partName = SkinBOXExtensions.GetBaseType(partName);
|
||||
return _posisioningInfos.ContainsKey(partName) ? _posisioningInfos[partName] : default;
|
||||
}
|
||||
|
||||
public static Vector3 GetSkinPartPivot(string partName) => GetPositioningInfo(partName).Pivot;
|
||||
|
||||
public static Vector3 GetSkinPartTranslation(string partName) => GetPositioningInfo(partName).Translation;
|
||||
|
||||
public const int GameTickInMilliseconds = 50;
|
||||
|
||||
// See: https://minecraft.fandom.com/wiki/Dye#Color_values for more information.
|
||||
public static readonly Color[] DyeColors = [
|
||||
Color.FromArgb(0xf9fffe), // White
|
||||
Color.FromArgb(0xf9801d), // Orange
|
||||
Color.FromArgb(0xc74ebd), // Magenta
|
||||
Color.FromArgb(0x3ab3da), // Light Blue
|
||||
Color.FromArgb(0xfed83d), // Yellow
|
||||
Color.FromArgb(0x80c71f), // Lime
|
||||
Color.FromArgb(0xf38baa), // Pink
|
||||
Color.FromArgb(0x474f52), // Gray
|
||||
Color.FromArgb(0x9d9d97), // Light Gray
|
||||
Color.FromArgb(0x169c9c), // Cyan
|
||||
Color.FromArgb(0x8932b8), // Purple
|
||||
Color.FromArgb(0x3c44aa), // Blue
|
||||
Color.FromArgb(0x835432), // Brown
|
||||
Color.FromArgb(0x5e7c16), // Green
|
||||
Color.FromArgb(0xb02e26), // Red
|
||||
Color.FromArgb(0x1d1d21), // Black
|
||||
];
|
||||
}
|
||||
}
|
||||
93
PckStudio.Core/IO/3DST/3DSTextureReader.cs
Normal file
93
PckStudio.Core/IO/3DST/3DSTextureReader.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OMI.Workers;
|
||||
using OMI;
|
||||
|
||||
namespace PckStudio.Core.IO._3DST
|
||||
{
|
||||
public class _3DSTextureReader : IDataFormatReader<Image>, IDataFormatReader
|
||||
{
|
||||
public Image FromFile(string filename)
|
||||
{
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
Image img = null;
|
||||
using (FileStream fs = File.OpenRead(filename))
|
||||
{
|
||||
img = FromStream(fs);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
throw new FileNotFoundException(filename);
|
||||
}
|
||||
|
||||
public Image FromStream(Stream stream)
|
||||
{
|
||||
Image img = null;
|
||||
using (var reader = new EndiannessAwareBinaryReader(stream, Encoding.ASCII, leaveOpen: true, Endianness.LittleEndian))
|
||||
{
|
||||
if (reader.ReadString(4) == "3DST")
|
||||
{
|
||||
const int offset = 32;
|
||||
reader.ReadInt32(); // unknown value
|
||||
_3DSTextureFormat format = reader.ReadInt32() switch
|
||||
{
|
||||
0 => _3DSTextureFormat.argb8,
|
||||
1 => _3DSTextureFormat.rgb8,
|
||||
2 => _3DSTextureFormat.rgba5551,
|
||||
3 => _3DSTextureFormat.rgb8,
|
||||
4 => _3DSTextureFormat.rgba4,
|
||||
9 => _3DSTextureFormat.la4,
|
||||
_ => _3DSTextureFormat.dontCare,
|
||||
};
|
||||
int width = reader.ReadInt32();
|
||||
int height = reader.ReadInt32();
|
||||
int bufferSize = CalcBufferSize(format, width, height);
|
||||
stream.Seek(offset, SeekOrigin.Begin);
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
reader.Read(buffer, 0, bufferSize);
|
||||
img = TextureCodec.Decode(buffer, width, height, format);
|
||||
img.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
||||
}
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
private static int CalcBufferSize(_3DSTextureFormat textureFormat, int width, int height)
|
||||
{
|
||||
switch (textureFormat)
|
||||
{
|
||||
case _3DSTextureFormat.argb8:
|
||||
return width * height * 4;
|
||||
case _3DSTextureFormat.rgb8:
|
||||
return width * height * 3;
|
||||
case _3DSTextureFormat.rgba5551:
|
||||
case _3DSTextureFormat.rgb565:
|
||||
case _3DSTextureFormat.rgba4:
|
||||
case _3DSTextureFormat.la8:
|
||||
case _3DSTextureFormat.hilo8:
|
||||
return width * height * 2;
|
||||
case _3DSTextureFormat.l8:
|
||||
case _3DSTextureFormat.a8:
|
||||
case _3DSTextureFormat.la4:
|
||||
case _3DSTextureFormat.etc1a4:
|
||||
return width * height;
|
||||
case _3DSTextureFormat.l4:
|
||||
case _3DSTextureFormat.a4:
|
||||
case _3DSTextureFormat.etc1:
|
||||
return width * height >> 1;
|
||||
default:
|
||||
throw new InvalidDataException("Invalid texture format on BCH!");
|
||||
}
|
||||
}
|
||||
|
||||
object IDataFormatReader.FromFile(string filename) => FromFile(filename);
|
||||
|
||||
object IDataFormatReader.FromStream(Stream stream) => FromStream(stream);
|
||||
}
|
||||
}
|
||||
46
PckStudio.Core/IO/3DST/3DSTextureWriter.cs
Normal file
46
PckStudio.Core/IO/3DST/3DSTextureWriter.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using OMI;
|
||||
using OMI.Workers;
|
||||
|
||||
namespace PckStudio.Core.IO._3DST
|
||||
{
|
||||
public class _3DSTextureWriter : IDataFormatWriter
|
||||
{
|
||||
private Image _image;
|
||||
private _3DSTextureFormat _format;
|
||||
public _3DSTextureWriter(Image image, _3DSTextureFormat format = _3DSTextureFormat.argb8)
|
||||
{
|
||||
_image = image;
|
||||
_format = format;
|
||||
}
|
||||
|
||||
public void WriteToFile(string filename)
|
||||
{
|
||||
using(FileStream fs = File.OpenWrite(filename))
|
||||
{
|
||||
WriteToStream(fs);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteToStream(Stream stream)
|
||||
{
|
||||
using (var writer = new EndiannessAwareBinaryWriter(stream, Encoding.ASCII, leaveOpen: true, Endianness.LittleEndian))
|
||||
{
|
||||
writer.WriteString("3DST"); // 0
|
||||
writer.Write(2); // 4 unknown
|
||||
writer.Write((int)_format); // 8
|
||||
writer.Write(_image.Width); // 12
|
||||
writer.Write(_image.Height); // 16
|
||||
writer.Write(0); // 20
|
||||
writer.Write(0); // 24
|
||||
writer.Write(0); // 28
|
||||
_image.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
||||
byte[] buffer = TextureCodec.Encode(new Bitmap(_image), _format);
|
||||
stream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
636
PckStudio.Core/IO/3DST/TextureCodec.cs
Normal file
636
PckStudio.Core/IO/3DST/TextureCodec.cs
Normal file
@@ -0,0 +1,636 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PckStudio.Core.IO._3DST
|
||||
{
|
||||
/// <summary>
|
||||
/// Format of the texture used on the PICA200.
|
||||
/// </summary>
|
||||
public enum _3DSTextureFormat
|
||||
{
|
||||
argb8 = 0,
|
||||
rgb8 = 1,
|
||||
rgba5551 = 2,
|
||||
rgb565 = 3,
|
||||
rgba4 = 4,
|
||||
la8 = 5,
|
||||
hilo8 = 6,
|
||||
l8 = 7,
|
||||
a8 = 8,
|
||||
la4 = 9,
|
||||
l4 = 0xa,
|
||||
a4 = 0xb,
|
||||
etc1 = 0xc,
|
||||
etc1a4 = 0xd,
|
||||
dontCare
|
||||
}
|
||||
|
||||
class TextureCodec
|
||||
{
|
||||
private static readonly int[] tileOrder = { 0, 1, 8, 9, 2, 3, 10, 11, 16, 17, 24, 25, 18, 19, 26, 27, 4, 5, 12, 13, 6, 7, 14, 15, 20, 21, 28, 29, 22, 23, 30, 31, 32, 33, 40, 41, 34, 35, 42, 43, 48, 49, 56, 57, 50, 51, 58, 59, 36, 37, 44, 45, 38, 39, 46, 47, 52, 53, 60, 61, 54, 55, 62, 63 };
|
||||
private static readonly int[,] etc1LUT = { { 2, 8, -2, -8 }, { 5, 17, -5, -17 }, { 9, 29, -9, -29 }, { 13, 42, -13, -42 }, { 18, 60, -18, -60 }, { 24, 80, -24, -80 }, { 33, 106, -33, -106 }, { 47, 183, -47, -183 } };
|
||||
|
||||
private static class TextureConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a Bitmap from a RGBA8 Texture buffer.
|
||||
/// </summary>
|
||||
/// <param name="array">The Buffer</param>
|
||||
/// <param name="width">Width of the Texture</param>
|
||||
/// <param name="height">Height of the Texture</param>
|
||||
/// <returns></returns>
|
||||
public static Bitmap ToBitmap(byte[] array, int width, int height)
|
||||
{
|
||||
Bitmap img = new Bitmap(width, height, PixelFormat.Format32bppArgb);
|
||||
BitmapData imgData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
|
||||
Marshal.Copy(array, 0, imgData.Scan0, array.Length);
|
||||
img.UnlockBits(imgData);
|
||||
return img;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a RGBA8 Texture Buffer from a Bitmap.
|
||||
/// </summary>
|
||||
/// <param name="img">The Bitmap</param>
|
||||
/// <returns></returns>
|
||||
public static byte[] ToArray(Bitmap img)
|
||||
{
|
||||
BitmapData imgData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
byte[] array = new byte[imgData.Stride * img.Height];
|
||||
Marshal.Copy(imgData.Scan0, array, 0, array.Length);
|
||||
img.UnlockBits(imgData);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a PICA200 Texture.
|
||||
/// </summary>
|
||||
/// <param name="data">Buffer with the Texture</param>
|
||||
/// <param name="width">Width of the Texture</param>
|
||||
/// <param name="height">Height of the Texture</param>
|
||||
/// <param name="format">Pixel Format of the Texture</param>
|
||||
/// <returns></returns>
|
||||
public static Bitmap Decode(byte[] data, int width, int height, _3DSTextureFormat format)
|
||||
{
|
||||
byte[] output = new byte[width * height * 4];
|
||||
int dataOffset = 0;
|
||||
bool toggle = false;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case _3DSTextureFormat.argb8:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
int outputOffset = ((tX * 8 + x) + ((tY * 8 + y) * width)) * 4;
|
||||
|
||||
Buffer.BlockCopy(data, dataOffset + 1, output, outputOffset, 3);
|
||||
output[outputOffset + 3] = data[dataOffset];
|
||||
dataOffset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.rgb8:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + ((tY * 8 + y) * width)) * 4;
|
||||
|
||||
Buffer.BlockCopy(data, dataOffset, output, (int)outputOffset, 3);
|
||||
output[outputOffset + 3] = 0xff;
|
||||
|
||||
dataOffset += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.rgba5551:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + (((tY * 8 + y)) * width)) * 4;
|
||||
|
||||
ushort pixelData = (ushort)(data[dataOffset] | (data[dataOffset + 1] << 8));
|
||||
|
||||
byte r = (byte)(((pixelData >> 1) & 0x1f) << 3);
|
||||
byte g = (byte)(((pixelData >> 6) & 0x1f) << 3);
|
||||
byte b = (byte)(((pixelData >> 11) & 0x1f) << 3);
|
||||
byte a = (byte)((pixelData & 1) * 0xff);
|
||||
|
||||
output[outputOffset] = (byte)(r | (r >> 5));
|
||||
output[outputOffset + 1] = (byte)(g | (g >> 5));
|
||||
output[outputOffset + 2] = (byte)(b | (b >> 5));
|
||||
output[outputOffset + 3] = a;
|
||||
|
||||
dataOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.rgb565:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + (((tY * 8 + y)) * width)) * 4;
|
||||
|
||||
ushort pixelData = (ushort)(data[dataOffset] | (data[dataOffset + 1] << 8));
|
||||
|
||||
byte r = (byte)((pixelData & 0x1f) << 3);
|
||||
byte g = (byte)(((pixelData >> 5) & 0x3f) << 2);
|
||||
byte b = (byte)(((pixelData >> 11) & 0x1f) << 3);
|
||||
|
||||
output[outputOffset] = (byte)(r | (r >> 5));
|
||||
output[outputOffset + 1] = (byte)(g | (g >> 6));
|
||||
output[outputOffset + 2] = (byte)(b | (b >> 5));
|
||||
output[outputOffset + 3] = 0xff;
|
||||
|
||||
dataOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.rgba4:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + (((tY * 8 + y)) * width)) * 4;
|
||||
|
||||
ushort pixelData = (ushort)(data[dataOffset] | (data[dataOffset + 1] << 8));
|
||||
|
||||
byte r = (byte)((pixelData >> 4) & 0xf);
|
||||
byte g = (byte)((pixelData >> 8) & 0xf);
|
||||
byte b = (byte)((pixelData >> 12) & 0xf);
|
||||
byte a = (byte)(pixelData & 0xf);
|
||||
|
||||
output[outputOffset] = (byte)(r | (r << 4));
|
||||
output[outputOffset + 1] = (byte)(g | (g << 4));
|
||||
output[outputOffset + 2] = (byte)(b | (b << 4));
|
||||
output[outputOffset + 3] = (byte)(a | (a << 4));
|
||||
|
||||
dataOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.la8:
|
||||
case _3DSTextureFormat.hilo8:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + (((tY * 8 + y)) * width)) * 4;
|
||||
|
||||
output[outputOffset] = data[dataOffset];
|
||||
output[outputOffset + 1] = data[dataOffset];
|
||||
output[outputOffset + 2] = data[dataOffset];
|
||||
output[outputOffset + 3] = data[dataOffset + 1];
|
||||
|
||||
dataOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.l8:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + (((tY * 8 + y)) * width)) * 4;
|
||||
|
||||
output[outputOffset] = data[dataOffset];
|
||||
output[outputOffset + 1] = data[dataOffset];
|
||||
output[outputOffset + 2] = data[dataOffset];
|
||||
output[outputOffset + 3] = 0xff;
|
||||
|
||||
dataOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.a8:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + (((tY * 8 + y)) * width)) * 4;
|
||||
|
||||
output[outputOffset] = 0xff;
|
||||
output[outputOffset + 1] = 0xff;
|
||||
output[outputOffset + 2] = 0xff;
|
||||
output[outputOffset + 3] = data[dataOffset];
|
||||
|
||||
dataOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.la4:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + (((tY * 8 + y)) * width)) * 4;
|
||||
|
||||
output[outputOffset] = (byte)(data[dataOffset] >> 4);
|
||||
output[outputOffset + 1] = (byte)(data[dataOffset] >> 4);
|
||||
output[outputOffset + 2] = (byte)(data[dataOffset] >> 4);
|
||||
output[outputOffset + 3] = (byte)(data[dataOffset] & 0xf);
|
||||
|
||||
dataOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.l4:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + (((tY * 8 + y)) * width)) * 4;
|
||||
|
||||
byte c = toggle ? (byte)((data[dataOffset++] & 0xf0) >> 4) : (byte)(data[dataOffset] & 0xf);
|
||||
toggle = !toggle;
|
||||
c = (byte)((c << 4) | c);
|
||||
output[outputOffset] = c;
|
||||
output[outputOffset + 1] = c;
|
||||
output[outputOffset + 2] = c;
|
||||
output[outputOffset + 3] = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.a4:
|
||||
for (int tY = 0; tY < height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
long outputOffset = ((tX * 8) + x + (((tY * 8 + y)) * width)) * 4;
|
||||
|
||||
output[outputOffset] = 0xff;
|
||||
output[outputOffset + 1] = 0xff;
|
||||
output[outputOffset + 2] = 0xff;
|
||||
byte a = toggle ? (byte)((data[dataOffset++] & 0xf0) >> 4) : (byte)(data[dataOffset] & 0xf);
|
||||
toggle = !toggle;
|
||||
output[outputOffset + 3] = (byte)((a << 4) | a);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case _3DSTextureFormat.etc1:
|
||||
case _3DSTextureFormat.etc1a4:
|
||||
byte[] decodedData = etc1Decode(data, width, height, format == _3DSTextureFormat.etc1a4);
|
||||
int[] etc1Order = etc1Scramble(width, height);
|
||||
|
||||
int i = 0;
|
||||
for (int tY = 0; tY < height / 4; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < width / 4; tX++)
|
||||
{
|
||||
int TX = etc1Order[i] % (width / 4);
|
||||
int TY = (etc1Order[i] - TX) / (width / 4);
|
||||
for (int y = 0; y < 4; y++)
|
||||
{
|
||||
for (int x = 0; x < 4; x++)
|
||||
{
|
||||
dataOffset = ((TX * 4) + x + (((TY * 4) + y) * width)) * 4;
|
||||
long outputOffset = ((tX * 4) + x + (((tY * 4 + y)) * width)) * 4;
|
||||
|
||||
Buffer.BlockCopy(decodedData, (int)dataOffset, output, (int)outputOffset, 4);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return TextureConverter.ToBitmap(output, width, height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a PICA200 Texture.
|
||||
/// </summary>
|
||||
/// <param name="img">Input image to be encoded</param>
|
||||
/// <param name="format">Pixel Format of the Texture</param>
|
||||
/// <returns></returns>
|
||||
public static byte[] Encode(Bitmap img, _3DSTextureFormat format)
|
||||
{
|
||||
byte[] data = TextureConverter.ToArray(img);
|
||||
byte[] output = new byte[data.Length];
|
||||
|
||||
int outputOffset = 0;
|
||||
switch (format)
|
||||
{
|
||||
case _3DSTextureFormat.argb8:
|
||||
for (int tY = 0; tY < img.Height / 8; tY++)
|
||||
{
|
||||
for (int tX = 0; tX < img.Width / 8; tX++)
|
||||
{
|
||||
for (int pixel = 0; pixel < 64; pixel++)
|
||||
{
|
||||
int x = tileOrder[pixel] % 8;
|
||||
int y = (tileOrder[pixel] - x) / 8;
|
||||
int dataOffset = ((tX * 8) + x + (tY * 8 + y) * img.Width) * 4;
|
||||
|
||||
Buffer.BlockCopy(data, dataOffset, output, outputOffset + 1, 3);
|
||||
output[outputOffset] = data[dataOffset + 3];
|
||||
outputOffset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default: throw new NotImplementedException(nameof(format));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#region "ETC1"
|
||||
private static byte[] etc1Decode(byte[] input, int width, int height, bool alpha)
|
||||
{
|
||||
byte[] output = new byte[(width * height * 4)];
|
||||
long offset = 0;
|
||||
|
||||
for (int y = 0; y < height / 4; y++)
|
||||
{
|
||||
for (int x = 0; x < width / 4; x++)
|
||||
{
|
||||
byte[] colorBlock = new byte[8];
|
||||
byte[] alphaBlock = new byte[8];
|
||||
if (alpha)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
colorBlock[7 - i] = input[offset + 8 + i];
|
||||
alphaBlock[i] = input[offset + i];
|
||||
}
|
||||
offset += 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
colorBlock[7 - i] = input[offset + i];
|
||||
alphaBlock[i] = 0xff;
|
||||
}
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
colorBlock = etc1DecodeBlock(colorBlock);
|
||||
|
||||
bool toggle = false;
|
||||
long alphaOffset = 0;
|
||||
for (int tX = 0; tX < 4; tX++)
|
||||
{
|
||||
for (int tY = 0; tY < 4; tY++)
|
||||
{
|
||||
int outputOffset = (x * 4 + tX + ((y * 4 + tY) * width)) * 4;
|
||||
int blockOffset = (tX + (tY * 4)) * 4;
|
||||
Buffer.BlockCopy(colorBlock, blockOffset, output, outputOffset, 3);
|
||||
|
||||
byte a = toggle ? (byte)((alphaBlock[alphaOffset++] & 0xf0) >> 4) : (byte)(alphaBlock[alphaOffset] & 0xf);
|
||||
output[outputOffset + 3] = (byte)((a << 4) | a);
|
||||
toggle = !toggle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static byte[] etc1DecodeBlock(byte[] data)
|
||||
{
|
||||
uint blockTop = BitConverter.ToUInt32(data, 0);
|
||||
uint blockBottom = BitConverter.ToUInt32(data, 4);
|
||||
|
||||
bool flip = (blockTop & 0x1000000) > 0;
|
||||
bool difference = (blockTop & 0x2000000) > 0;
|
||||
|
||||
uint r1, g1, b1;
|
||||
uint r2, g2, b2;
|
||||
|
||||
if (difference)
|
||||
{
|
||||
r1 = blockTop & 0xf8;
|
||||
g1 = (blockTop & 0xf800) >> 8;
|
||||
b1 = (blockTop & 0xf80000) >> 16;
|
||||
|
||||
r2 = (uint)((sbyte)(r1 >> 3) + ((sbyte)((blockTop & 7) << 5) >> 5));
|
||||
g2 = (uint)((sbyte)(g1 >> 3) + ((sbyte)((blockTop & 0x700) >> 3) >> 5));
|
||||
b2 = (uint)((sbyte)(b1 >> 3) + ((sbyte)((blockTop & 0x70000) >> 11) >> 5));
|
||||
|
||||
r1 |= r1 >> 5;
|
||||
g1 |= g1 >> 5;
|
||||
b1 |= b1 >> 5;
|
||||
|
||||
r2 = (r2 << 3) | (r2 >> 2);
|
||||
g2 = (g2 << 3) | (g2 >> 2);
|
||||
b2 = (b2 << 3) | (b2 >> 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
r1 = blockTop & 0xf0;
|
||||
g1 = (blockTop & 0xf000) >> 8;
|
||||
b1 = (blockTop & 0xf00000) >> 16;
|
||||
|
||||
r2 = (blockTop & 0xf) << 4;
|
||||
g2 = (blockTop & 0xf00) >> 4;
|
||||
b2 = (blockTop & 0xf0000) >> 12;
|
||||
|
||||
r1 |= r1 >> 4;
|
||||
g1 |= g1 >> 4;
|
||||
b1 |= b1 >> 4;
|
||||
|
||||
r2 |= r2 >> 4;
|
||||
g2 |= g2 >> 4;
|
||||
b2 |= b2 >> 4;
|
||||
}
|
||||
|
||||
uint table1 = (blockTop >> 29) & 7;
|
||||
uint table2 = (blockTop >> 26) & 7;
|
||||
|
||||
byte[] output = new byte[(4 * 4 * 4)];
|
||||
if (!flip)
|
||||
{
|
||||
for (int y = 0; y <= 3; y++)
|
||||
{
|
||||
for (int x = 0; x <= 1; x++)
|
||||
{
|
||||
Color color1 = etc1Pixel(r1, g1, b1, x, y, blockBottom, table1);
|
||||
Color color2 = etc1Pixel(r2, g2, b2, x + 2, y, blockBottom, table2);
|
||||
|
||||
int offset1 = (y * 4 + x) * 4;
|
||||
output[offset1] = color1.B;
|
||||
output[offset1 + 1] = color1.G;
|
||||
output[offset1 + 2] = color1.R;
|
||||
|
||||
int offset2 = (y * 4 + x + 2) * 4;
|
||||
output[offset2] = color2.B;
|
||||
output[offset2 + 1] = color2.G;
|
||||
output[offset2 + 2] = color2.R;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y <= 1; y++)
|
||||
{
|
||||
for (int x = 0; x <= 3; x++)
|
||||
{
|
||||
Color color1 = etc1Pixel(r1, g1, b1, x, y, blockBottom, table1);
|
||||
Color color2 = etc1Pixel(r2, g2, b2, x, y + 2, blockBottom, table2);
|
||||
|
||||
int offset1 = (y * 4 + x) * 4;
|
||||
output[offset1] = color1.B;
|
||||
output[offset1 + 1] = color1.G;
|
||||
output[offset1 + 2] = color1.R;
|
||||
|
||||
int offset2 = ((y + 2) * 4 + x) * 4;
|
||||
output[offset2] = color2.B;
|
||||
output[offset2 + 1] = color2.G;
|
||||
output[offset2 + 2] = color2.R;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static Color etc1Pixel(uint r, uint g, uint b, int x, int y, uint block, uint table)
|
||||
{
|
||||
int index = x * 4 + y;
|
||||
uint MSB = block << 1;
|
||||
|
||||
int pixel = index < 8
|
||||
? etc1LUT[table, ((block >> (index + 24)) & 1) + ((MSB >> (index + 8)) & 2)]
|
||||
: etc1LUT[table, ((block >> (index + 8)) & 1) + ((MSB >> (index - 8)) & 2)];
|
||||
|
||||
r = saturate((int)(r + pixel));
|
||||
g = saturate((int)(g + pixel));
|
||||
b = saturate((int)(b + pixel));
|
||||
|
||||
return Color.FromArgb((int)r, (int)g, (int)b);
|
||||
}
|
||||
|
||||
private static byte saturate(int value)
|
||||
{
|
||||
if (value > 0xff)
|
||||
return 0xff;
|
||||
if (value < 0)
|
||||
return 0;
|
||||
return (byte)(value & 0xff);
|
||||
}
|
||||
|
||||
private static int[] etc1Scramble(int width, int height)
|
||||
{
|
||||
//Maybe theres a better way to do this?
|
||||
int[] tileScramble = new int[((width / 4) * (height / 4))];
|
||||
int baseAccumulator = 0;
|
||||
int rowAccumulator = 0;
|
||||
int baseNumber = 0;
|
||||
int rowNumber = 0;
|
||||
|
||||
for (int tile = 0; tile < tileScramble.Length; tile++)
|
||||
{
|
||||
if ((tile % (width / 4) == 0) && tile > 0)
|
||||
{
|
||||
if (rowAccumulator < 1)
|
||||
{
|
||||
rowAccumulator += 1;
|
||||
rowNumber += 2;
|
||||
baseNumber = rowNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
rowAccumulator = 0;
|
||||
baseNumber -= 2;
|
||||
rowNumber = baseNumber;
|
||||
}
|
||||
}
|
||||
|
||||
tileScramble[tile] = baseNumber;
|
||||
|
||||
if (baseAccumulator < 1)
|
||||
{
|
||||
baseAccumulator++;
|
||||
baseNumber++;
|
||||
}
|
||||
else
|
||||
{
|
||||
baseAccumulator = 0;
|
||||
baseNumber += 3;
|
||||
}
|
||||
}
|
||||
|
||||
return tileScramble;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
139
PckStudio.Core/IO/PckAudio/PckAudioFileReader.cs
Normal file
139
PckStudio.Core/IO/PckAudio/PckAudioFileReader.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using OMI;
|
||||
using OMI.Workers;
|
||||
using PckStudio.Core.FileFormats;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace PckStudio.Core.IO.PckAudio
|
||||
{
|
||||
|
||||
public class InvalidAudioPckException : Exception
|
||||
{
|
||||
public InvalidAudioPckException(string message) : base(message)
|
||||
{ }
|
||||
}
|
||||
|
||||
public class PckAudioFileReader : IDataFormatReader<PckAudioFile>, IDataFormatReader
|
||||
{
|
||||
private PckAudioFile _file;
|
||||
private Endianness _endianness;
|
||||
private List<string> LUT = new List<string>();
|
||||
private List<PckAudioFile.AudioCategory.EAudioType> _OriginalAudioTypeOrder = new List<PckAudioFile.AudioCategory.EAudioType>();
|
||||
|
||||
public PckAudioFileReader(Endianness endianness)
|
||||
{
|
||||
_endianness = endianness;
|
||||
}
|
||||
|
||||
public PckAudioFile FromFile(string filename)
|
||||
{
|
||||
if(File.Exists(filename))
|
||||
{
|
||||
PckAudioFile file;
|
||||
using(FileStream fs = File.OpenRead(filename))
|
||||
{
|
||||
file = FromStream(fs);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
throw new FileNotFoundException(filename);
|
||||
}
|
||||
|
||||
public PckAudioFile FromStream(Stream stream)
|
||||
{
|
||||
using (var reader = new EndiannessAwareBinaryReader(stream,
|
||||
_endianness == Endianness.BigEndian
|
||||
? Encoding.BigEndianUnicode
|
||||
: Encoding.Unicode,
|
||||
leaveOpen: true, _endianness))
|
||||
{
|
||||
int pck_type = reader.ReadInt32();
|
||||
if (pck_type > 0xf00000) // 03 00 00 00 == true
|
||||
throw new OverflowException(nameof(pck_type));
|
||||
if (pck_type > 1)
|
||||
throw new InvalidAudioPckException(nameof(pck_type));
|
||||
_file = new PckAudioFile();
|
||||
ReadLookUpTable(reader);
|
||||
ReadCategories(reader);
|
||||
ReadCategorySongs(reader);
|
||||
}
|
||||
return _file;
|
||||
}
|
||||
|
||||
private void ReadLookUpTable(EndiannessAwareBinaryReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
LUT = new List<string>(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int index = reader.ReadInt32();
|
||||
string value = ReadString(reader);
|
||||
LUT.Insert(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadCategories(EndiannessAwareBinaryReader reader)
|
||||
{
|
||||
int categoryEntryCount = reader.ReadInt32();
|
||||
for (; 0 < categoryEntryCount; categoryEntryCount--)
|
||||
{
|
||||
var parameterType = (PckAudioFile.AudioCategory.EAudioParameterType)reader.ReadInt32();
|
||||
var audioType = (PckAudioFile.AudioCategory.EAudioType)reader.ReadInt32();
|
||||
string name = ReadString(reader);
|
||||
// AddCategory puts the file's categories out of order and causes some songs to be put in the wrong categories
|
||||
// This is my simple fix for the issue.
|
||||
_OriginalAudioTypeOrder.Add(audioType);
|
||||
_file.AddCategory(parameterType, audioType, name);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadCategorySongs(EndiannessAwareBinaryReader reader)
|
||||
{
|
||||
List<string> credits = new List<string>();
|
||||
List<string> creditIds = new List<string>();
|
||||
foreach (PckAudioFile.AudioCategory.EAudioType c in _OriginalAudioTypeOrder)
|
||||
{
|
||||
int audioCount = reader.ReadInt32();
|
||||
for (; 0 < audioCount; audioCount--)
|
||||
{
|
||||
string key = LUT[reader.ReadInt32()];
|
||||
string value = ReadString(reader);
|
||||
switch (key)
|
||||
{
|
||||
case "CUENAME":
|
||||
_file.GetCategory(c).SongNames.Add(value);
|
||||
break;
|
||||
case "CREDIT":
|
||||
credits.Add(value);
|
||||
break;
|
||||
case "CREDITID":
|
||||
creditIds.Add(value);
|
||||
_file.AddCreditId(value);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidDataException(nameof(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ((string str, string id) credit in credits.Zip(creditIds, (str, id) => (str, id)))
|
||||
{
|
||||
_file.SetCredit(credit.id, credit.str);
|
||||
}
|
||||
}
|
||||
|
||||
private string ReadString(EndiannessAwareBinaryReader reader)
|
||||
{
|
||||
int len = reader.ReadInt32();
|
||||
string s = reader.ReadString(len);
|
||||
reader.ReadInt32(); // padding
|
||||
return s;
|
||||
}
|
||||
|
||||
object IDataFormatReader.FromStream(Stream stream) => FromStream(stream);
|
||||
|
||||
object IDataFormatReader.FromFile(string filename) => FromFile(filename);
|
||||
}
|
||||
}
|
||||
104
PckStudio.Core/IO/PckAudio/PckAudioFileWriter.cs
Normal file
104
PckStudio.Core/IO/PckAudio/PckAudioFileWriter.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using OMI;
|
||||
using OMI.Workers;
|
||||
using PckStudio.Core.FileFormats;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace PckStudio.Core.IO.PckAudio
|
||||
{
|
||||
public class PckAudioFileWriter : IDataFormatWriter
|
||||
{
|
||||
|
||||
private PckAudioFile _file;
|
||||
private Endianness _endianness;
|
||||
private static readonly List<string> LUT = new List<string>
|
||||
{
|
||||
"CUENAME",
|
||||
"CREDIT",
|
||||
"CREDITID"
|
||||
};
|
||||
|
||||
public PckAudioFileWriter(PckAudioFile file, Endianness endianness)
|
||||
{
|
||||
_file = file;
|
||||
_endianness = endianness;
|
||||
}
|
||||
|
||||
public void WriteToFile(string filename)
|
||||
{
|
||||
using (FileStream fs = File.OpenWrite(filename))
|
||||
{
|
||||
WriteToStream(fs);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteToStream(Stream stream)
|
||||
{
|
||||
using (var writer = new EndiannessAwareBinaryWriter(stream,
|
||||
_endianness == Endianness.BigEndian
|
||||
? Encoding.BigEndianUnicode
|
||||
: Encoding.Unicode,
|
||||
leaveOpen: true, _endianness))
|
||||
{
|
||||
writer.Write(_file.type);
|
||||
WriteLookUpTable(writer);
|
||||
WriteCategories(writer);
|
||||
WriteCategorySongs(writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteString(EndiannessAwareBinaryWriter writer, string s)
|
||||
{
|
||||
writer.Write(s.Length);
|
||||
writer.WriteString(s);
|
||||
writer.Write(0); // padding
|
||||
}
|
||||
|
||||
private void WriteLookUpTable(EndiannessAwareBinaryWriter writer)
|
||||
{
|
||||
writer.Write(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
writer.Write(i);
|
||||
WriteString(writer, LUT[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCategories(EndiannessAwareBinaryWriter writer)
|
||||
{
|
||||
writer.Write(_file.Categories.Length);
|
||||
foreach (PckAudioFile.AudioCategory category in _file.Categories)
|
||||
{
|
||||
writer.Write((int)category.parameterType);
|
||||
writer.Write((int)category.AudioType);
|
||||
WriteString(writer, category.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCategorySongs(EndiannessAwareBinaryWriter writer)
|
||||
{
|
||||
bool addCredit = true;
|
||||
foreach (PckAudioFile.AudioCategory category in _file.Categories)
|
||||
{
|
||||
writer.Write(category.SongNames.Count + (addCredit ? _file.Credits.Count * 2 : 0));
|
||||
foreach (var name in category.SongNames)
|
||||
{
|
||||
writer.Write(LUT.IndexOf("CUENAME"));
|
||||
WriteString(writer, name);
|
||||
}
|
||||
if (addCredit)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> credit in _file.Credits)
|
||||
{
|
||||
writer.Write(LUT.IndexOf("CREDIT"));
|
||||
WriteString(writer, credit.Value);
|
||||
writer.Write(LUT.IndexOf("CREDITID"));
|
||||
WriteString(writer, credit.Key);
|
||||
}
|
||||
}
|
||||
addCredit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
PckStudio.Core/IO/TGA/TGADataTypeCode.cs
Normal file
60
PckStudio.Core/IO/TGA/TGADataTypeCode.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
/* Copyright (c) 2022-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
internal enum TGADataTypeCode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// No image data included.
|
||||
/// </summary>
|
||||
NO_DATA = 0,
|
||||
/// <summary>
|
||||
/// Uncompressed, color-mapped images.
|
||||
/// </summary>
|
||||
COLORMAPPED = 1,
|
||||
/// <summary>
|
||||
/// Uncompressed, RGB images.
|
||||
/// </summary>
|
||||
RGB = 2,
|
||||
/// <summary>
|
||||
/// Uncompressed, black and white images.
|
||||
/// </summary>
|
||||
BLACK_WHITE = 3,
|
||||
/// <summary>
|
||||
/// Runlength encoded color-mapped images.
|
||||
/// </summary>
|
||||
RLE_COLORMAPPED = 9,
|
||||
/// <summary>
|
||||
/// Runlength encoded RGB images.
|
||||
/// </summary>
|
||||
RLE_RGB = 10,
|
||||
/// <summary>
|
||||
/// Compressed, black and white images.
|
||||
/// </summary>
|
||||
COMPRESSED_BLACK_WHITE = 11,
|
||||
/// <summary>
|
||||
/// Compressed color-mapped data, using Huffman, Delta, and runlength encoding.
|
||||
/// </summary>
|
||||
COMPRESSED_RLE_COLORMAPPED = 32,
|
||||
/// <summary>
|
||||
/// Compressed color-mapped data, using Huffman, Delta, and runlength encoding. 4-pass quadtree-type process.
|
||||
/// </summary>
|
||||
COMPRESSED_RLE_COLORMAPPED_4 = 33,
|
||||
}
|
||||
}
|
||||
33
PckStudio.Core/IO/TGA/TGADeserializer.cs
Normal file
33
PckStudio.Core/IO/TGA/TGADeserializer.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
/* Copyright (c) 2022-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
internal static class TGADeserializer
|
||||
{
|
||||
private static TGAReader _reader = new TGAReader();
|
||||
|
||||
public static Image DeserializeFromStream(Stream stream)
|
||||
{
|
||||
TGAFileData tgaImg = _reader.FromStream(stream);
|
||||
return tgaImg.Bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
PckStudio.Core/IO/TGA/TGAException.cs
Normal file
25
PckStudio.Core/IO/TGA/TGAException.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
[Serializable]
|
||||
internal class TGAException : Exception
|
||||
{
|
||||
public TGAException()
|
||||
{
|
||||
}
|
||||
|
||||
public TGAException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public TGAException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected TGAException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
63
PckStudio.Core/IO/TGA/TGAExtentionData.cs
Normal file
63
PckStudio.Core/IO/TGA/TGAExtentionData.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
/* Copyright (c) 2022-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
internal struct TGAExtentionData
|
||||
{
|
||||
public const short ExtensionSize = 0x1EF;
|
||||
public string AuthorName;
|
||||
public string AuthorComment;
|
||||
public DateTime TimeStamp;
|
||||
public string JobID;
|
||||
public TimeSpan JobTime;
|
||||
public string SoftwareID;
|
||||
public byte[] SoftwareVersion;
|
||||
public int KeyColor;
|
||||
public int PixelAspectRatio;
|
||||
public int GammaValue;
|
||||
public int ColorCorrectionOffset;
|
||||
public int PostageStampOffset;
|
||||
public int ScanLineOffset;
|
||||
public byte AttributesType;
|
||||
|
||||
public static TGAExtentionData Create()
|
||||
{
|
||||
var extensionData = new TGAExtentionData();
|
||||
extensionData.AuthorName = "";
|
||||
extensionData.AuthorComment = "";
|
||||
extensionData.AuthorComment = "";
|
||||
extensionData.TimeStamp = DateTime.Now;
|
||||
extensionData.JobID = "";
|
||||
extensionData.JobTime = new TimeSpan(extensionData.TimeStamp.Hour, extensionData.TimeStamp.Minute, extensionData.TimeStamp.Second);
|
||||
extensionData.SoftwareID = Application.ProductName;
|
||||
Version.TryParse(Application.ProductVersion, out Version currentVersion);
|
||||
extensionData.SoftwareVersion = new byte[] { (byte)currentVersion.Major, (byte)currentVersion.Minor, (byte)currentVersion.Build };
|
||||
extensionData.KeyColor = 0;
|
||||
extensionData.PixelAspectRatio = 0;
|
||||
extensionData.GammaValue = 0;
|
||||
extensionData.ColorCorrectionOffset = 0;
|
||||
extensionData.PostageStampOffset = 0;
|
||||
extensionData.ScanLineOffset = 0;
|
||||
extensionData.AttributesType = 3;
|
||||
return extensionData;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
PckStudio.Core/IO/TGA/TGAFileData.cs
Normal file
40
PckStudio.Core/IO/TGA/TGAFileData.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
/* Copyright (c) 2022-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
internal class TGAFileData
|
||||
{
|
||||
public TGAFileData(TGAHeader header, Image bitmap, TGAFooter footer, TGAExtentionData extentionData)
|
||||
{
|
||||
if (bitmap.Width != header.Width || bitmap.Height != header.Height)
|
||||
throw new InvalidDataException("Header resolution doesn't match Image resolution");
|
||||
Header = header;
|
||||
Bitmap = bitmap;
|
||||
Footer = footer;
|
||||
ExtentionData = extentionData;
|
||||
}
|
||||
|
||||
public readonly TGAHeader Header;
|
||||
public readonly Image Bitmap;
|
||||
public readonly TGAFooter Footer;
|
||||
public readonly TGAExtentionData ExtentionData;
|
||||
}
|
||||
}
|
||||
27
PckStudio.Core/IO/TGA/TGAFooter.cs
Normal file
27
PckStudio.Core/IO/TGA/TGAFooter.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
/* Copyright (c) 2022-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
internal struct TGAFooter
|
||||
{
|
||||
internal const string Signature = "TRUEVISION-XFILE";
|
||||
public int ExtensionDataOffset;
|
||||
public int DeveloperAreaDataOffset;
|
||||
}
|
||||
}
|
||||
37
PckStudio.Core/IO/TGA/TGAHeader.cs
Normal file
37
PckStudio.Core/IO/TGA/TGAHeader.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
/* Copyright (c) 2022-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
/// <summary>
|
||||
/// Resources:
|
||||
/// <http://www.paulbourke.net/dataformats/tga/>
|
||||
/// <https://en.wikipedia.org/wiki/Truevision_TGA>
|
||||
/// </summary>
|
||||
internal struct TGAHeader
|
||||
{
|
||||
public byte[] Id;
|
||||
public TGADataTypeCode DataTypeCode;
|
||||
public (byte Type, short Origin/*Offset*/, short Length, byte Depth) Colormap;
|
||||
public (short X, short Y) Origin;
|
||||
public short Width;
|
||||
public short Height;
|
||||
public byte BitsPerPixel;
|
||||
public byte ImageDescriptor;
|
||||
}
|
||||
}
|
||||
240
PckStudio.Core/IO/TGA/TGAReader.cs
Normal file
240
PckStudio.Core/IO/TGA/TGAReader.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
/* Copyright (c) 2022-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Drawing;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using OMI.Workers;
|
||||
using OMI;
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
internal class TGAReader : IDataFormatReader<TGAFileData>, IDataFormatReader
|
||||
{
|
||||
object IDataFormatReader.FromStream(Stream stream) => FromStream(stream);
|
||||
|
||||
object IDataFormatReader.FromFile(string filename) => FromFile(filename);
|
||||
|
||||
public TGAFileData FromFile(string filename)
|
||||
{
|
||||
if (File.Exists(filename))
|
||||
{
|
||||
using(FileStream fs = File.OpenRead(filename) )
|
||||
{
|
||||
return FromStream(fs);
|
||||
}
|
||||
}
|
||||
throw new FileNotFoundException(filename);
|
||||
}
|
||||
|
||||
public TGAFileData FromStream(Stream stream)
|
||||
{
|
||||
using var reader = new EndiannessAwareBinaryReader(stream, Encoding.ASCII, leaveOpen: true, Endianness.LittleEndian);
|
||||
TGAHeader header = LoadHeader(reader);
|
||||
Image image = LoadImage(reader, header);
|
||||
TGAFooter footer = LoadFooter(reader);
|
||||
TGAExtentionData extentionData = LoadExtentionData(reader, footer);
|
||||
return new TGAFileData(header, image, footer, extentionData);
|
||||
}
|
||||
|
||||
private static void TGA_HandleRGB(EndiannessAwareBinaryReader reader, TGAHeader header, BitmapData bitmapData)
|
||||
{
|
||||
int bytesPerPixel = header.BitsPerPixel / 8;
|
||||
|
||||
byte[] data = reader.ReadBytes(header.Height * header.Width * bytesPerPixel);
|
||||
Marshal.Copy(data, 0, bitmapData.Scan0, data.Length);
|
||||
}
|
||||
|
||||
private static void TGA_HandleNoData(EndiannessAwareBinaryReader _, TGAHeader header, BitmapData bitmapData)
|
||||
{
|
||||
Random r = new Random();
|
||||
byte[] bytes = new byte[bitmapData.Width * bitmapData.Height * 4];
|
||||
r.NextBytes(bytes);
|
||||
Marshal.Copy(bytes, 0, bitmapData.Scan0, bytes.Length);
|
||||
}
|
||||
|
||||
private static TGAHeader LoadHeader(EndiannessAwareBinaryReader reader)
|
||||
{
|
||||
var header = new TGAHeader();
|
||||
byte[] bytes = reader.ReadBytes(3);
|
||||
(var headerIdLength, header.Colormap.Type, header.DataTypeCode) = (bytes[0], bytes[1], (TGADataTypeCode)bytes[2]);
|
||||
header.Colormap.Origin = reader.ReadInt16();
|
||||
header.Colormap.Length = reader.ReadInt16();
|
||||
header.Colormap.Depth = reader.ReadByte();
|
||||
header.Origin.X = reader.ReadInt16();
|
||||
header.Origin.Y = reader.ReadInt16();
|
||||
header.Width = reader.ReadInt16();
|
||||
header.Height = reader.ReadInt16();
|
||||
header.BitsPerPixel = reader.ReadByte();
|
||||
header.ImageDescriptor = reader.ReadByte();
|
||||
header.Id = reader.ReadBytes(headerIdLength);
|
||||
DebugLogHeader(header);
|
||||
return header;
|
||||
}
|
||||
|
||||
private static PixelFormat GetPixelFormat(int bytesPerPixel)
|
||||
{
|
||||
return bytesPerPixel switch
|
||||
{
|
||||
2 => PixelFormat.Format16bppArgb1555,
|
||||
3 => PixelFormat.Format24bppRgb,
|
||||
4 => PixelFormat.Format32bppArgb,
|
||||
_ => throw new NotSupportedException(nameof(bytesPerPixel))
|
||||
};
|
||||
}
|
||||
|
||||
private static Image LoadImage(EndiannessAwareBinaryReader reader, TGAHeader header)
|
||||
{
|
||||
if (header.DataTypeCode != TGADataTypeCode.RGB)
|
||||
throw new NotSupportedException(nameof(header.DataTypeCode));
|
||||
|
||||
Bitmap bitmap = new Bitmap(header.Width, header.Height);
|
||||
BitmapData bitmapData = bitmap.LockBits(
|
||||
new Rectangle(0, 0, header.Width, header.Height),
|
||||
ImageLockMode.WriteOnly,
|
||||
GetPixelFormat(header.BitsPerPixel >> 3));
|
||||
|
||||
if (header.DataTypeCode == TGADataTypeCode.NO_DATA)
|
||||
{
|
||||
TGA_HandleNoData(reader, header, bitmapData);
|
||||
bitmap.UnlockBits(bitmapData);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
TGA_HandleRGB(reader, header, bitmapData);
|
||||
bitmap.UnlockBits(bitmapData);
|
||||
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private static TGAFooter LoadFooter(EndiannessAwareBinaryReader reader)
|
||||
{
|
||||
long origin = reader.BaseStream.Position;
|
||||
reader.BaseStream.Seek(-26, SeekOrigin.End);
|
||||
|
||||
TGAFooter footer = new TGAFooter();
|
||||
|
||||
footer.ExtensionDataOffset = reader.ReadInt32(); // optional
|
||||
footer.DeveloperAreaDataOffset = reader.ReadInt32(); // optional
|
||||
string signature = reader.ReadString(16);
|
||||
Debug.Assert(signature.Equals(TGAFooter.Signature) || reader.ReadInt16() != 0x002E,
|
||||
"Footer signature invalid");
|
||||
reader.BaseStream.Seek(origin, SeekOrigin.Begin);
|
||||
DebugLogFooter(footer);
|
||||
return footer;
|
||||
}
|
||||
|
||||
private static TGAExtentionData LoadExtentionData(EndiannessAwareBinaryReader reader, TGAFooter footer)
|
||||
{
|
||||
if (footer.ExtensionDataOffset > 0)
|
||||
{
|
||||
reader.BaseStream.Seek(footer.ExtensionDataOffset, SeekOrigin.Begin);
|
||||
if (reader.ReadInt16() == TGAExtentionData.ExtensionSize)
|
||||
{
|
||||
TGAExtentionData extentionData = new TGAExtentionData();
|
||||
extentionData.AuthorName = reader.ReadString(41);
|
||||
extentionData.AuthorComment = reader.ReadString(324);
|
||||
short month = reader.ReadInt16();
|
||||
short day = reader.ReadInt16();
|
||||
short year = reader.ReadInt16();
|
||||
short hour = reader.ReadInt16();
|
||||
short minute = reader.ReadInt16();
|
||||
short second = reader.ReadInt16();
|
||||
extentionData.TimeStamp = new DateTime(year, month, day, hour, minute, second);
|
||||
extentionData.JobID = reader.ReadString(41);
|
||||
extentionData.JobTime = new TimeSpan(
|
||||
hours: reader.ReadInt16(),
|
||||
minutes: reader.ReadInt16(),
|
||||
seconds: reader.ReadInt16()
|
||||
);
|
||||
extentionData.SoftwareID = reader.ReadString(41);
|
||||
extentionData.SoftwareVersion = reader.ReadBytes(3);
|
||||
extentionData.KeyColor = reader.ReadInt32();
|
||||
extentionData.PixelAspectRatio = reader.ReadInt32();
|
||||
extentionData.GammaValue = reader.ReadInt32();
|
||||
extentionData.ColorCorrectionOffset = reader.ReadInt32();
|
||||
extentionData.PostageStampOffset = reader.ReadInt32();
|
||||
extentionData.ScanLineOffset = reader.ReadInt32();
|
||||
extentionData.AttributesType = reader.ReadByte();
|
||||
DebugLogExtentionData(extentionData);
|
||||
return extentionData;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[DebuggerHidden]
|
||||
[DebuggerStepThrough]
|
||||
private static void DebugLogExtentionData(TGAExtentionData extentionData)
|
||||
{
|
||||
Debug.WriteLine("-------Extention Data-------", category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Author Name: {0}", args: extentionData.AuthorName), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Author Comment: {0}", args: extentionData.AuthorComment), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Time Stamp: {0}", args: extentionData.TimeStamp), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Job ID: {0}", args: extentionData.JobID), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Job Time: {0}", args: extentionData.JobTime), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("SoftwareID: {0}", args: extentionData.SoftwareID), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Software Version: {0}.{1}.{2}", extentionData.SoftwareVersion[0], extentionData.SoftwareVersion[1], extentionData.SoftwareVersion[2]), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Key Color: {0}", args: extentionData.KeyColor), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Pixel Aspect Ratio: {0}", args: extentionData.PixelAspectRatio), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Gamma Value: {0}", args: extentionData.GammaValue), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Color Correction Offset: {0}", args: extentionData.ColorCorrectionOffset), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Postage Stamp Offset: {0}", args: extentionData.PostageStampOffset), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Scan Line Offset: {0}", args: extentionData.ScanLineOffset), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Attributes Type: {0}", args: extentionData.AttributesType), category: nameof(TGAReader));
|
||||
Debug.WriteLine("----------------------------", category: nameof(TGAReader));
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[DebuggerHidden]
|
||||
[DebuggerStepThrough]
|
||||
private static void DebugLogHeader(TGAHeader header)
|
||||
{
|
||||
Debug.WriteLine("------Header Data------", category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("ID length: {0}", args: header.Id.Length), category: nameof(TGAReader));
|
||||
Debug.WriteLineIf(header.Id.Length > 0, $"ID: {header.Id}", category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Colourmap type: {0}", args: header.Colormap.Type), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Image type: {0}", args: header.DataTypeCode), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Colour map offset: {0}", args: header.Colormap.Origin), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Colour map length: {0}", args: header.Colormap.Length), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Colour map depth: {0}", args: header.Colormap.Depth), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("X origin: {0}", args: header.Origin.X), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Y origin: {0}", args: header.Origin.Y), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Width: {0}", args: header.Width), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Height: {0}", args: header.Height), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Bits per pixel: {0}", args: header.BitsPerPixel), category: nameof(TGAReader));
|
||||
Debug.WriteLine(string.Format("Descriptor: {0}", args: header.ImageDescriptor), category: nameof(TGAReader));
|
||||
Debug.WriteLine("-----------------------", category: nameof(TGAReader));
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
[DebuggerHidden]
|
||||
[DebuggerStepThrough]
|
||||
private static void DebugLogFooter(TGAFooter footer)
|
||||
{
|
||||
Debug.WriteLine("-------Footer Data-------", category: nameof(TGAReader));
|
||||
Debug.WriteLine($"Extension Data Offset: {footer.ExtensionDataOffset:x}", category: nameof(TGAReader));
|
||||
Debug.WriteLine($"Developer Area Data Offset: {footer.DeveloperAreaDataOffset:x}", category: nameof(TGAReader));
|
||||
Debug.WriteLine("-----------------------", category: nameof(TGAReader));
|
||||
}
|
||||
}
|
||||
}
|
||||
32
PckStudio.Core/IO/TGA/TGASerializer.cs
Normal file
32
PckStudio.Core/IO/TGA/TGASerializer.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
/* Copyright (c) 2022-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
internal static class TGASerializer
|
||||
{
|
||||
private static TGAWriter _writer = new TGAWriter();
|
||||
|
||||
public static void SerializeToStream(Stream stream, Image image)
|
||||
{
|
||||
_writer.WriteToStream(stream, image);
|
||||
}
|
||||
}
|
||||
}
|
||||
140
PckStudio.Core/IO/TGA/TGAWriter.cs
Normal file
140
PckStudio.Core/IO/TGA/TGAWriter.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
/* Copyright (c) 2022-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using OMI;
|
||||
|
||||
namespace PckStudio.Core.IO.TGA
|
||||
{
|
||||
internal class TGAWriter
|
||||
{
|
||||
private Bitmap _bitmap;
|
||||
private int extensionDataOffset = 0;
|
||||
|
||||
public TGAWriter()
|
||||
{
|
||||
}
|
||||
|
||||
private void WriteHeader(EndiannessAwareBinaryWriter writer)
|
||||
{
|
||||
writer.Write(new byte[]
|
||||
{
|
||||
0, // header.Id.Length
|
||||
0, // colormap type
|
||||
(byte)TGADataTypeCode.RGB
|
||||
});
|
||||
writer.Write(0); // Colormap.Origin
|
||||
writer.Write(0); // Colormap.Length
|
||||
writer.Write(0); // Colormap.Depth
|
||||
writer.Write(0); // Origin.X
|
||||
writer.Write(0); // Origin.Y
|
||||
writer.Write(_bitmap.Width);
|
||||
writer.Write(_bitmap.Height);
|
||||
writer.Write(32); // BitsPerPixel
|
||||
writer.Write(8); // ImageDescriptor
|
||||
}
|
||||
|
||||
private void WriteImage(EndiannessAwareBinaryWriter writer)
|
||||
{
|
||||
_bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
||||
BitmapData bitmapData = _bitmap.LockBits(
|
||||
new Rectangle(0, 0, _bitmap.Width, _bitmap.Height),
|
||||
ImageLockMode.ReadOnly,
|
||||
PixelFormat.Format32bppArgb);
|
||||
|
||||
byte[] buffer = new byte[_bitmap.Width * _bitmap.Height * 4];
|
||||
Marshal.Copy(bitmapData.Scan0, buffer, 0, _bitmap.Width * _bitmap.Height * 4);
|
||||
writer.Write(buffer);
|
||||
}
|
||||
|
||||
private void WriteFooter(EndiannessAwareBinaryWriter writer)
|
||||
{
|
||||
writer.Write(extensionDataOffset); // extensionDataOffset
|
||||
writer.Write(0); // developerAreaDataOffset
|
||||
writer.WriteString(TGAFooter.Signature);
|
||||
writer.Write((byte)0x2E);
|
||||
writer.Write((byte)0x00);
|
||||
}
|
||||
|
||||
private void WriteExtensionData(EndiannessAwareBinaryWriter writer)
|
||||
{
|
||||
extensionDataOffset = Convert.ToInt32(writer.BaseStream.Position);
|
||||
TGAExtentionData extentionData = TGAExtentionData.Create();
|
||||
writer.Write(TGAExtentionData.ExtensionSize);
|
||||
// Author Name
|
||||
writer.WriteString(extentionData.AuthorName, 41);
|
||||
// Author Comment
|
||||
writer.WriteString(extentionData.AuthorComment, 324);
|
||||
// Timestamp
|
||||
writer.Write((short)extentionData.TimeStamp.Month);
|
||||
writer.Write((short)extentionData.TimeStamp.Day);
|
||||
writer.Write((short)extentionData.TimeStamp.Year);
|
||||
writer.Write((short)extentionData.TimeStamp.Hour);
|
||||
writer.Write((short)extentionData.TimeStamp.Minute);
|
||||
writer.Write((short)extentionData.TimeStamp.Second);
|
||||
// Job id
|
||||
writer.WriteString(extentionData.JobID, 41);
|
||||
// Job time
|
||||
writer.Write((short)extentionData.JobTime.Hours);
|
||||
writer.Write((short)extentionData.JobTime.Minutes);
|
||||
writer.Write((short)extentionData.JobTime.Seconds);
|
||||
// Software Id
|
||||
writer.WriteString(extentionData.SoftwareID, 41);
|
||||
// Software version
|
||||
writer.Write(extentionData.SoftwareVersion, 0, 3);
|
||||
// Key color
|
||||
writer.Write(extentionData.KeyColor);
|
||||
// Pixel aspect ratio
|
||||
writer.Write(extentionData.PixelAspectRatio);
|
||||
// Gamma value
|
||||
writer.Write(extentionData.GammaValue);
|
||||
// Color correction offset
|
||||
writer.Write(extentionData.ColorCorrectionOffset);
|
||||
// Postage stamp offset
|
||||
writer.Write(extentionData.PostageStampOffset);
|
||||
// Scan line offset
|
||||
writer.Write(extentionData.ScanLineOffset);
|
||||
// Attributes type
|
||||
writer.Write(extentionData.AttributesType);
|
||||
}
|
||||
|
||||
public void WriteToStream(Stream stream, Image image)
|
||||
{
|
||||
_bitmap = new Bitmap(image);
|
||||
using (var writer = new EndiannessAwareBinaryWriter(stream, Encoding.ASCII, leaveOpen: true, Endianness.LittleEndian))
|
||||
{
|
||||
WriteHeader(writer);
|
||||
WriteImage(writer);
|
||||
WriteExtensionData(writer);
|
||||
WriteFooter(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteToFile(string filename, Image image)
|
||||
{
|
||||
using (FileStream fs = File.OpenWrite(filename))
|
||||
{
|
||||
WriteToStream(fs, image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
PckStudio.Core/Interfaces/IEditor.cs
Normal file
24
PckStudio.Core/Interfaces/IEditor.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OMI.Formats.Pck;
|
||||
|
||||
namespace PckStudio.Interfaces
|
||||
{
|
||||
public interface IEditor<T> where T : class
|
||||
{
|
||||
T EditorValue { get; }
|
||||
|
||||
ISaveContext<T> SaveContext { get; }
|
||||
|
||||
void Save();
|
||||
|
||||
void SaveAs();
|
||||
|
||||
void Close();
|
||||
|
||||
void UpdateView();
|
||||
}
|
||||
}
|
||||
22
PckStudio.Core/Interfaces/IModelImportProvider.cs
Normal file
22
PckStudio.Core/Interfaces/IModelImportProvider.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using PckStudio.Core;
|
||||
|
||||
namespace PckStudio.Interfaces
|
||||
{
|
||||
public interface IModelImportProvider<T> where T : class
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public FileDialogFilter DialogFilter { get; }
|
||||
|
||||
public bool SupportImport { get; }
|
||||
public bool SupportExport { get; }
|
||||
|
||||
public T Import(string filename);
|
||||
public T Import(Stream stream);
|
||||
|
||||
public void Export(string filename, T model);
|
||||
public void Export(ref Stream stream, T model);
|
||||
}
|
||||
}
|
||||
9
PckStudio.Core/Interfaces/IPckAssetDeserializer.cs
Normal file
9
PckStudio.Core/Interfaces/IPckAssetDeserializer.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using OMI.Formats.Pck;
|
||||
|
||||
namespace PckStudio.Interfaces
|
||||
{
|
||||
public interface IPckAssetDeserializer<T>
|
||||
{
|
||||
public T Deserialize(PckAsset asset);
|
||||
}
|
||||
}
|
||||
9
PckStudio.Core/Interfaces/IPckAssetSerializer.cs
Normal file
9
PckStudio.Core/Interfaces/IPckAssetSerializer.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using OMI.Formats.Pck;
|
||||
|
||||
namespace PckStudio.Interfaces
|
||||
{
|
||||
public interface IPckAssetSerializer<T>
|
||||
{
|
||||
public void Serialize(T obj, ref PckAsset asset);
|
||||
}
|
||||
}
|
||||
9
PckStudio.Core/Interfaces/ISaveContext.cs
Normal file
9
PckStudio.Core/Interfaces/ISaveContext.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace PckStudio.Interfaces
|
||||
{
|
||||
public interface ISaveContext<T>
|
||||
{
|
||||
public bool AutoSave { get; }
|
||||
|
||||
public void Save(T value);
|
||||
}
|
||||
}
|
||||
65
PckStudio.Core/Interfaces/ITryGetSet.cs
Normal file
65
PckStudio.Core/Interfaces/ITryGetSet.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
namespace PckStudio.Interfaces
|
||||
{
|
||||
public delegate bool TryGetDelegate<in TKey, TValue>(TKey key, out TValue value);
|
||||
public delegate bool TrySetDelegate<in TKey, TValue>(TKey key, TValue value);
|
||||
|
||||
public sealed class TryGet<TKey, TValue> : ITryGet<TKey, TValue>
|
||||
{
|
||||
private TryGetDelegate<TKey, TValue> _tryGetDelegate;
|
||||
|
||||
public static ITryGet<TKey, TValue> FromDelegate(TryGetDelegate<TKey, TValue> tryGetDelegate) => new TryGet<TKey, TValue>(tryGetDelegate);
|
||||
|
||||
bool ITryGet<TKey, TValue>.TryGet(TKey key, out TValue value) => _tryGetDelegate(key, out value);
|
||||
|
||||
private TryGet(TryGetDelegate<TKey, TValue> tryGetDelegate)
|
||||
{
|
||||
_tryGetDelegate = tryGetDelegate;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TrySet<TKey, TValue> : ITrySet<TKey, TValue>
|
||||
{
|
||||
private TrySetDelegate<TKey, TValue> _trySetDelegate;
|
||||
|
||||
public static ITrySet<TKey, TValue> FromDelegate(TrySetDelegate<TKey, TValue> trySetDelegate) => new TrySet<TKey, TValue>(trySetDelegate);
|
||||
|
||||
bool ITrySet<TKey, TValue>.TrySet(TKey key, TValue value) => _trySetDelegate(key, value);
|
||||
|
||||
private TrySet(TrySetDelegate<TKey, TValue> trySetDelegate)
|
||||
{
|
||||
_trySetDelegate = trySetDelegate;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TryGetSet<TKey, TValue> : ITryGetSet<TKey, TValue>
|
||||
{
|
||||
public static ITryGetSet<TKey, TValue> FromDelegates(TryGetDelegate<TKey, TValue> tryGetDelegate, TrySetDelegate<TKey, TValue> trySetDelegate) => new TryGetSet<TKey, TValue>(tryGetDelegate, trySetDelegate);
|
||||
|
||||
public bool TryGet(TKey key, out TValue value) => _tryGetDelegate(key, out value);
|
||||
|
||||
public bool TrySet(TKey key, TValue value) => _trySetDelegate(key, value);
|
||||
|
||||
private TryGetDelegate<TKey, TValue> _tryGetDelegate;
|
||||
private TrySetDelegate<TKey, TValue> _trySetDelegate;
|
||||
|
||||
private TryGetSet(TryGetDelegate<TKey, TValue> tryGetDelegate, TrySetDelegate<TKey, TValue> trySetDelegate)
|
||||
{
|
||||
_tryGetDelegate = tryGetDelegate;
|
||||
_trySetDelegate = trySetDelegate;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITryGet<in TKey, TValue>
|
||||
{
|
||||
bool TryGet(TKey key, out TValue value);
|
||||
}
|
||||
|
||||
public interface ITrySet<in TKey, TValue>
|
||||
{
|
||||
bool TrySet(TKey key, TValue value);
|
||||
}
|
||||
|
||||
public interface ITryGetSet<in TKey, TValue> : ITryGet<TKey, TValue>, ITrySet<TKey, TValue>
|
||||
{
|
||||
}
|
||||
}
|
||||
90
PckStudio.Core/ItemSelectionPopUp.Designer.cs
generated
Normal file
90
PckStudio.Core/ItemSelectionPopUp.Designer.cs
generated
Normal file
@@ -0,0 +1,90 @@
|
||||
namespace PckStudio.Core.Additional_Popups
|
||||
{
|
||||
partial class ItemSelectionPopUp
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ItemSelectionPopUp));
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.okBtn = new System.Windows.Forms.Button();
|
||||
this.cancelButton = new System.Windows.Forms.Button();
|
||||
this.ComboBox = new System.Windows.Forms.ComboBox();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// label2
|
||||
//
|
||||
resources.ApplyResources(this.label2, "label2");
|
||||
this.label2.ForeColor = System.Drawing.Color.White;
|
||||
this.label2.Name = "label2";
|
||||
//
|
||||
// okBtn
|
||||
//
|
||||
resources.ApplyResources(this.okBtn, "okBtn");
|
||||
this.okBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.okBtn.Name = "okBtn";
|
||||
this.okBtn.UseVisualStyleBackColor = true;
|
||||
this.okBtn.Click += new System.EventHandler(this.okBtn_Click);
|
||||
//
|
||||
// cancelButton
|
||||
//
|
||||
resources.ApplyResources(this.cancelButton, "cancelButton");
|
||||
this.cancelButton.ForeColor = System.Drawing.Color.White;
|
||||
this.cancelButton.Name = "cancelButton";
|
||||
this.cancelButton.UseVisualStyleBackColor = true;
|
||||
this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
|
||||
//
|
||||
// ComboBox
|
||||
//
|
||||
this.ComboBox.FormattingEnabled = true;
|
||||
resources.ApplyResources(this.ComboBox, "ComboBox");
|
||||
this.ComboBox.Name = "ComboBox";
|
||||
//
|
||||
// ItemSelectionPopUp
|
||||
//
|
||||
resources.ApplyResources(this, "$this");
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ControlBox = false;
|
||||
this.Controls.Add(this.ComboBox);
|
||||
this.Controls.Add(this.cancelButton);
|
||||
this.Controls.Add(this.okBtn);
|
||||
this.Controls.Add(this.label2);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "ItemSelectionPopUp";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Button cancelButton;
|
||||
private System.Windows.Forms.ComboBox ComboBox;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.Button okBtn;
|
||||
}
|
||||
}
|
||||
44
PckStudio.Core/ItemSelectionPopUp.cs
Normal file
44
PckStudio.Core/ItemSelectionPopUp.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace PckStudio.Core.Additional_Popups
|
||||
{
|
||||
public partial class ItemSelectionPopUp : Form
|
||||
{
|
||||
public string SelectedItem => DialogResult == DialogResult.OK ? ComboBox.Text : string.Empty;
|
||||
public int SelectedIndex => DialogResult == DialogResult.OK ? ComboBox.SelectedIndex : -1;
|
||||
|
||||
public string LabelText
|
||||
{
|
||||
get => label2.Text;
|
||||
set => label2.Text = value;
|
||||
}
|
||||
public string ButtonText
|
||||
{
|
||||
get => okBtn.Text;
|
||||
set => okBtn.Text = value;
|
||||
}
|
||||
|
||||
public ItemSelectionPopUp(params string[] items)
|
||||
{
|
||||
InitializeComponent();
|
||||
ComboBox.Items.AddRange(items);
|
||||
}
|
||||
|
||||
private void okBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
if(ComboBox.SelectedIndex <= -1)
|
||||
{
|
||||
cancelButton_Click(sender, e);
|
||||
return;
|
||||
}
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
private void cancelButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
2755
PckStudio.Core/ItemSelectionPopUp.resx
Normal file
2755
PckStudio.Core/ItemSelectionPopUp.resx
Normal file
File diff suppressed because it is too large
Load Diff
21
PckStudio.Core/Json/ColorEntry.cs
Normal file
21
PckStudio.Core/Json/ColorEntry.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PckStudio.Core.Json
|
||||
{
|
||||
public class JsonColorEntry
|
||||
{
|
||||
[JsonProperty("defaultName", Required = Required.Always)]
|
||||
public string DefaultName { get; set; }
|
||||
|
||||
[JsonProperty("isWaterColour", DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public bool IsWaterColour { get; set; }
|
||||
|
||||
[JsonProperty("variants", DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public string[] Variants { get; set; }
|
||||
}
|
||||
}
|
||||
20
PckStudio.Core/Json/EntityInfo.cs
Normal file
20
PckStudio.Core/Json/EntityInfo.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PckStudio.Core.Json
|
||||
{
|
||||
public class EntityInfo
|
||||
{
|
||||
[JsonProperty("displayName")]
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[JsonProperty("internalName")]
|
||||
public string InternalName { get; set; }
|
||||
|
||||
public EntityInfo(string displayName, string internalName)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
InternalName = internalName;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
PckStudio.Core/Json/TileInfo.cs
Normal file
39
PckStudio.Core/Json/TileInfo.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PckStudio.Core.Json
|
||||
{
|
||||
public sealed class JsonTileInfo
|
||||
{
|
||||
[JsonProperty("displayName")]
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[JsonProperty("internalName")]
|
||||
public string InternalName { get; set; }
|
||||
|
||||
[JsonProperty("width")]
|
||||
public int TileWidth { get; set; } = 1;
|
||||
|
||||
[JsonProperty("height")]
|
||||
public int TileHeight { get; set; } = 1;
|
||||
|
||||
[JsonProperty("hasColourEntry", DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public bool HasColourEntry { get; set; }
|
||||
|
||||
[JsonProperty("colourEntry", DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public JsonColorEntry ColourEntry { get; set; }
|
||||
|
||||
[JsonProperty("allowCustomColour", DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||
public bool AllowCustomColour { get; set; }
|
||||
|
||||
public JsonTileInfo(string displayName, string internalName)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
InternalName = internalName;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
PckStudio.Core/Json/UpdateInformation.cs
Normal file
24
PckStudio.Core/Json/UpdateInformation.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PckStudio.Core.Json
|
||||
{
|
||||
internal class UpdateInformation
|
||||
{
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[JsonProperty("changelog")]
|
||||
public string Changelog { get; set; }
|
||||
|
||||
[JsonProperty("mandatory")]
|
||||
public bool Mandatory { get; set; }
|
||||
}
|
||||
}
|
||||
66
PckStudio.Core/Misc/FileCacher.cs
Normal file
66
PckStudio.Core/Misc/FileCacher.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
/* Copyright (c) 2023-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace PckStudio.Core.Misc
|
||||
{
|
||||
public class FileCacher
|
||||
{
|
||||
private string _cacheDirectory;
|
||||
|
||||
public string CacheDirectory => _cacheDirectory;
|
||||
|
||||
public FileCacher(string cacheDirectory)
|
||||
{
|
||||
_cacheDirectory = cacheDirectory;
|
||||
}
|
||||
|
||||
public bool HasFileCached(string filename)
|
||||
{
|
||||
string destinationFilepath = Path.Combine(_cacheDirectory, filename);
|
||||
return File.Exists(destinationFilepath);
|
||||
}
|
||||
|
||||
public string GetCachedFilepath(string filename)
|
||||
{
|
||||
if (HasFileCached(filename))
|
||||
{
|
||||
return Path.Combine(_cacheDirectory, filename);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Caches data if the <paramref name="filename"/> doesn't already exists in the <see cref="CacheDirectory"/>
|
||||
/// </summary>
|
||||
/// <param name="data">Data to cache</param>
|
||||
/// <param name="filename">Filename with extension</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public void Cache(byte[] data, string filename)
|
||||
{
|
||||
_ = filename ?? throw new ArgumentNullException("filename");
|
||||
string destinationFilepath = Path.Combine(_cacheDirectory, filename);
|
||||
if (!File.Exists(destinationFilepath))
|
||||
{
|
||||
using FileStream fsDst = File.OpenWrite(destinationFilepath);
|
||||
fsDst.Write(data, 0, data.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
202
PckStudio.Core/Misc/OpenFolderDialog.cs
Normal file
202
PckStudio.Core/Misc/OpenFolderDialog.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Source by: Simon Mourier(https://stackoverflow.com/users/403671/simon-mourier)
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
|
||||
namespace PckStudio.Core.Misc
|
||||
{
|
||||
public class OpenFolderDialog
|
||||
{
|
||||
public virtual string ResultPath { get; protected set; }
|
||||
public virtual string ResultName { get; protected set; }
|
||||
public virtual string InputPath { get; set; }
|
||||
public virtual bool ForceFileSystem { get; set; }
|
||||
public virtual string Title { get; set; }
|
||||
public virtual string OkButtonLabel { get; set; }
|
||||
public virtual string FileNameLabel { get; set; }
|
||||
|
||||
protected virtual int SetOptions(int options)
|
||||
{
|
||||
if (ForceFileSystem)
|
||||
{
|
||||
options |= (int)FOS.FOS_FORCEFILESYSTEM;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
// for all .NET
|
||||
public virtual bool? ShowDialog(IntPtr owner, bool throwOnError = false)
|
||||
{
|
||||
var dialog = (IFileOpenDialog)new FileOpenDialog();
|
||||
if (!string.IsNullOrEmpty(InputPath))
|
||||
{
|
||||
if (CheckHr(SHCreateItemFromParsingName(InputPath, null, typeof(IShellItem).GUID, out IShellItem item), throwOnError) != 0)
|
||||
return null;
|
||||
|
||||
dialog.SetFolder(item);
|
||||
}
|
||||
|
||||
FOS options = FOS.FOS_PICKFOLDERS;
|
||||
options = (FOS)SetOptions((int)options);
|
||||
dialog.SetOptions(options);
|
||||
|
||||
if (Title != null)
|
||||
{
|
||||
dialog.SetTitle(Title);
|
||||
}
|
||||
|
||||
if (OkButtonLabel != null)
|
||||
{
|
||||
dialog.SetOkButtonLabel(OkButtonLabel);
|
||||
}
|
||||
|
||||
if (FileNameLabel != null)
|
||||
{
|
||||
dialog.SetFileName(FileNameLabel);
|
||||
}
|
||||
|
||||
if (owner == IntPtr.Zero)
|
||||
{
|
||||
owner = Process.GetCurrentProcess().MainWindowHandle;
|
||||
if (owner == IntPtr.Zero)
|
||||
{
|
||||
owner = GetDesktopWindow();
|
||||
}
|
||||
}
|
||||
|
||||
var hr = dialog.Show(owner);
|
||||
if (hr == ERROR_CANCELLED)
|
||||
return null;
|
||||
|
||||
if (CheckHr(hr, throwOnError) != 0)
|
||||
return null;
|
||||
|
||||
if (CheckHr(dialog.GetResult(out IShellItem result), throwOnError) != 0)
|
||||
return null;
|
||||
|
||||
if (CheckHr(result.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, out var path), throwOnError) != 0)
|
||||
return null;
|
||||
|
||||
ResultPath = path;
|
||||
|
||||
if (CheckHr(result.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEEDITING, out path), false) == 0)
|
||||
{
|
||||
ResultName = path;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int CheckHr(int hr, bool throwOnError)
|
||||
{
|
||||
if (hr != 0)
|
||||
{
|
||||
if (throwOnError)
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
[DllImport("shell32")]
|
||||
private static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);
|
||||
|
||||
[DllImport("user32")]
|
||||
private static extern IntPtr GetDesktopWindow();
|
||||
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
private const int ERROR_CANCELLED = unchecked((int)0x800704C7);
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
|
||||
[ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] // CLSID_FileOpenDialog
|
||||
private class FileOpenDialog
|
||||
{
|
||||
}
|
||||
|
||||
[ComImport, Guid("42f85136-db7e-439c-85f1-e4075d135fc8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface IFileOpenDialog
|
||||
{
|
||||
[PreserveSig] int Show(IntPtr parent); // IModalWindow
|
||||
[PreserveSig] int SetFileTypes(); // not fully defined
|
||||
[PreserveSig] int SetFileTypeIndex(int iFileType);
|
||||
[PreserveSig] int GetFileTypeIndex(out int piFileType);
|
||||
[PreserveSig] int Advise(); // not fully defined
|
||||
[PreserveSig] int Unadvise();
|
||||
[PreserveSig] int SetOptions(FOS fos);
|
||||
[PreserveSig] int GetOptions(out FOS pfos);
|
||||
[PreserveSig] int SetDefaultFolder(IShellItem psi);
|
||||
[PreserveSig] int SetFolder(IShellItem psi);
|
||||
[PreserveSig] int GetFolder(out IShellItem ppsi);
|
||||
[PreserveSig] int GetCurrentSelection(out IShellItem ppsi);
|
||||
[PreserveSig] int SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||
[PreserveSig] int GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
|
||||
[PreserveSig] int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
|
||||
[PreserveSig] int SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
|
||||
[PreserveSig] int SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
|
||||
[PreserveSig] int GetResult(out IShellItem ppsi);
|
||||
[PreserveSig] int AddPlace(IShellItem psi, int alignment);
|
||||
[PreserveSig] int SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
|
||||
[PreserveSig] int Close(int hr);
|
||||
[PreserveSig] int SetClientGuid(); // not fully defined
|
||||
[PreserveSig] int ClearClientData();
|
||||
[PreserveSig] int SetFilter([MarshalAs(UnmanagedType.IUnknown)] object pFilter);
|
||||
[PreserveSig] int GetResults([MarshalAs(UnmanagedType.IUnknown)] out object ppenum);
|
||||
[PreserveSig] int GetSelectedItems([MarshalAs(UnmanagedType.IUnknown)] out object ppsai);
|
||||
}
|
||||
|
||||
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
private interface IShellItem
|
||||
{
|
||||
[PreserveSig] int BindToHandler(); // not fully defined
|
||||
[PreserveSig] int GetParent(); // not fully defined
|
||||
[PreserveSig] int GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
|
||||
[PreserveSig] int GetAttributes(); // not fully defined
|
||||
[PreserveSig] int Compare(); // not fully defined
|
||||
}
|
||||
|
||||
#pragma warning disable CA1712 // Do not prefix enum values with type name
|
||||
private enum SIGDN : uint
|
||||
{
|
||||
SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000,
|
||||
SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000,
|
||||
SIGDN_FILESYSPATH = 0x80058000,
|
||||
SIGDN_NORMALDISPLAY = 0,
|
||||
SIGDN_PARENTRELATIVE = 0x80080001,
|
||||
SIGDN_PARENTRELATIVEEDITING = 0x80031001,
|
||||
SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
|
||||
SIGDN_PARENTRELATIVEPARSING = 0x80018001,
|
||||
SIGDN_URL = 0x80068000
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum FOS
|
||||
{
|
||||
FOS_OVERWRITEPROMPT = 0x2,
|
||||
FOS_STRICTFILETYPES = 0x4,
|
||||
FOS_NOCHANGEDIR = 0x8,
|
||||
FOS_PICKFOLDERS = 0x20,
|
||||
FOS_FORCEFILESYSTEM = 0x40,
|
||||
FOS_ALLNONSTORAGEITEMS = 0x80,
|
||||
FOS_NOVALIDATE = 0x100,
|
||||
FOS_ALLOWMULTISELECT = 0x200,
|
||||
FOS_PATHMUSTEXIST = 0x800,
|
||||
FOS_FILEMUSTEXIST = 0x1000,
|
||||
FOS_CREATEPROMPT = 0x2000,
|
||||
FOS_SHAREAWARE = 0x4000,
|
||||
FOS_NOREADONLYRETURN = 0x8000,
|
||||
FOS_NOTESTFILECREATE = 0x10000,
|
||||
FOS_HIDEMRUPLACES = 0x20000,
|
||||
FOS_HIDEPINNEDPLACES = 0x40000,
|
||||
FOS_NODEREFERENCELINKS = 0x100000,
|
||||
FOS_OKBUTTONNEEDSINTERACTION = 0x200000,
|
||||
FOS_DONTADDTORECENT = 0x2000000,
|
||||
FOS_FORCESHOWHIDDEN = 0x10000000,
|
||||
FOS_DEFAULTNOMINIMODE = 0x20000000,
|
||||
FOS_FORCEPREVIEWPANEON = 0x40000000,
|
||||
FOS_SUPPORTSTREAMABLEITEMS = unchecked((int)0x80000000)
|
||||
}
|
||||
#pragma warning restore CA1712 // Do not prefix enum values with type name
|
||||
}
|
||||
}
|
||||
45
PckStudio.Core/PackInfo.cs
Normal file
45
PckStudio.Core/PackInfo.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using OMI.Formats.Pck;
|
||||
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public sealed class PackInfo
|
||||
{
|
||||
public static readonly PackInfo Empty = new PackInfo(default, default, default);
|
||||
public bool IsValid { get; }
|
||||
public PckFile File { get; }
|
||||
public OMI.Endianness Endianness { get; }
|
||||
|
||||
|
||||
//public enum PackType
|
||||
//{
|
||||
// Unknown = -1,
|
||||
// SkinPack,
|
||||
// TexturePack,
|
||||
// MashUpPack
|
||||
//}
|
||||
|
||||
//public PackType Type { get; }
|
||||
|
||||
public bool AllowEndianSwap { get; }
|
||||
|
||||
public static PackInfo Create(PckFile file, OMI.Endianness endianness, bool allowEndianSwap)
|
||||
{
|
||||
return new PackInfo(file, endianness, allowEndianSwap);
|
||||
}
|
||||
|
||||
private PackInfo(PckFile file, OMI.Endianness endianness, bool allowEndianSwap)
|
||||
{
|
||||
File = file;
|
||||
Endianness = endianness;
|
||||
AllowEndianSwap = allowEndianSwap;
|
||||
//Type = GetPackType();
|
||||
IsValid = file is not null && Enum.IsDefined(typeof(OMI.Endianness), endianness); // && Type != PackType.Unknown;
|
||||
}
|
||||
|
||||
//private PackType GetPackType()
|
||||
//{
|
||||
// return PackType.SkinPack;
|
||||
//}
|
||||
}
|
||||
}
|
||||
148
PckStudio.Core/PckStudio.Core.csproj
Normal file
148
PckStudio.Core/PckStudio.Core.csproj
Normal file
@@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{345EABED-F0D1-4D04-B409-BABDEF747352}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>PckStudio.Core</RootNamespace>
|
||||
<AssemblyName>PckStudio.Core</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<LangVersion>12</LangVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Animation.cs" />
|
||||
<Compile Include="App\SettingsManager.cs" />
|
||||
<Compile Include="App\Updater.cs" />
|
||||
<Compile Include="BoundingBox.cs" />
|
||||
<Compile Include="DelegatedFileSaveContext.cs" />
|
||||
<Compile Include="DelegatedSaveContext.cs" />
|
||||
<Compile Include="Deserializer\AnimationDeserializer.cs" />
|
||||
<Compile Include="Deserializer\ImageDeserializer.cs" />
|
||||
<Compile Include="Extensions\AnimationExtensions.cs" />
|
||||
<Compile Include="Extensions\BlendMode.cs" />
|
||||
<Compile Include="Extensions\BoundingBoxExtensions.cs" />
|
||||
<Compile Include="Extensions\ColorExtensions.cs" />
|
||||
<Compile Include="Extensions\CursorExtensions.cs" />
|
||||
<Compile Include="Extensions\EnumerableExtensions.cs" />
|
||||
<Compile Include="Extensions\GraphicsExtensions.cs" />
|
||||
<Compile Include="Extensions\ImageExtensions.cs" />
|
||||
<Compile Include="Extensions\ImageLayoutDirection.cs" />
|
||||
<Compile Include="Extensions\ImageSection.cs" />
|
||||
<Compile Include="Extensions\ListExtensions.cs" />
|
||||
<Compile Include="Extensions\LocFileExtensions.cs" />
|
||||
<Compile Include="Extensions\MaterialContainerExtensions.cs" />
|
||||
<Compile Include="Extensions\MaterialExtensions.cs" />
|
||||
<Compile Include="Extensions\MathExtensions.cs" />
|
||||
<Compile Include="Extensions\ModelBoxExtension.cs" />
|
||||
<Compile Include="Extensions\OpenTKExtensions.cs" />
|
||||
<Compile Include="Extensions\PckAssetExtensions.cs" />
|
||||
<Compile Include="Extensions\PictureBoxExtensions.cs" />
|
||||
<Compile Include="Extensions\SkinBOXExtensions.cs" />
|
||||
<Compile Include="Extensions\SkinExtensions.cs" />
|
||||
<Compile Include="Extensions\System.Numerics.cs" />
|
||||
<Compile Include="Extensions\TreeNodeExtensions.cs" />
|
||||
<Compile Include="Extensions\TreeViewExtensions.cs" />
|
||||
<Compile Include="FileDialogFilter.cs" />
|
||||
<Compile Include="FileFormats\PckAudioFile.cs" />
|
||||
<Compile Include="GameConstants.cs" />
|
||||
<Compile Include="Interfaces\IEditor.cs" />
|
||||
<Compile Include="Interfaces\IModelImportProvider.cs" />
|
||||
<Compile Include="Interfaces\IPckAssetDeserializer.cs" />
|
||||
<Compile Include="Interfaces\IPckAssetSerializer.cs" />
|
||||
<Compile Include="Interfaces\ISaveContext.cs" />
|
||||
<Compile Include="Interfaces\ITryGetSet.cs" />
|
||||
<Compile Include="IO\3DST\3DSTextureReader.cs" />
|
||||
<Compile Include="IO\3DST\3DSTextureWriter.cs" />
|
||||
<Compile Include="IO\3DST\TextureCodec.cs" />
|
||||
<Compile Include="IO\PckAudio\PckAudioFileReader.cs" />
|
||||
<Compile Include="IO\PckAudio\PckAudioFileWriter.cs" />
|
||||
<Compile Include="IO\TGA\TGADataTypeCode.cs" />
|
||||
<Compile Include="IO\TGA\TGADeserializer.cs" />
|
||||
<Compile Include="IO\TGA\TGAException.cs" />
|
||||
<Compile Include="IO\TGA\TGAExtentionData.cs" />
|
||||
<Compile Include="IO\TGA\TGAFileData.cs" />
|
||||
<Compile Include="IO\TGA\TGAFooter.cs" />
|
||||
<Compile Include="IO\TGA\TGAHeader.cs" />
|
||||
<Compile Include="IO\TGA\TGAReader.cs" />
|
||||
<Compile Include="IO\TGA\TGASerializer.cs" />
|
||||
<Compile Include="IO\TGA\TGAWriter.cs" />
|
||||
<Compile Include="ItemSelectionPopUp.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ItemSelectionPopUp.Designer.cs" />
|
||||
<Compile Include="Json\ColorEntry.cs" />
|
||||
<Compile Include="Json\EntityInfo.cs" />
|
||||
<Compile Include="Json\TileInfo.cs" />
|
||||
<Compile Include="Json\UpdateInformation.cs" />
|
||||
<Compile Include="Misc\FileCacher.cs" />
|
||||
<Compile Include="Misc\OpenFolderDialog.cs" />
|
||||
<Compile Include="PackInfo.cs" />
|
||||
<Compile Include="ResourceCategory.cs" />
|
||||
<Compile Include="ResourceLocation.cs" />
|
||||
<Compile Include="Serializer\AnimationSerializer.cs" />
|
||||
<Compile Include="Serializer\ImageSerializer.cs" />
|
||||
<Compile Include="Skin\Skin.cs" />
|
||||
<Compile Include="Skin\SkinANIM.cs" />
|
||||
<Compile Include="Skin\SkinAnimFlag.cs" />
|
||||
<Compile Include="Skin\SkinAnimMask.cs" />
|
||||
<Compile Include="Skin\SkinBOX.cs" />
|
||||
<Compile Include="Skin\SkinIdentifier.cs" />
|
||||
<Compile Include="Skin\SkinMetaData.cs" />
|
||||
<Compile Include="Skin\SkinModel.cs" />
|
||||
<Compile Include="Skin\SkinPartOffset.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AnimatedGif" Version="1.0.5" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>13.0.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Autoupdater.NET.Official">
|
||||
<Version>1.9.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="OpenTK" Version="3.3.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Vendor\OMI-Lib\OMI Filetypes Library\OMI Filetype Library.csproj">
|
||||
<Project>{693aebc1-293d-4df0-bcae-26a1099fe7bb}</Project>
|
||||
<Name>OMI Filetype Library</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
39
PckStudio.Core/ResourceCategory.cs
Normal file
39
PckStudio.Core/ResourceCategory.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Copyright (c) 2023-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public enum ResourceCategory
|
||||
{
|
||||
Unknown = -1,
|
||||
ItemAnimation,
|
||||
BlockAnimation,
|
||||
MobEntityTextures,
|
||||
ItemEntityTextures,
|
||||
ItemAtlas,
|
||||
BlockAtlas,
|
||||
ParticleAtlas,
|
||||
BannerAtlas,
|
||||
PaintingAtlas,
|
||||
ExplosionAtlas,
|
||||
ExperienceOrbAtlas,
|
||||
MoonPhaseAtlas,
|
||||
MapIconAtlas,
|
||||
AdditionalMapIconsAtlas,
|
||||
}
|
||||
}
|
||||
129
PckStudio.Core/ResourceLocation.cs
Normal file
129
PckStudio.Core/ResourceLocation.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
/* Copyright (c) 2024-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace PckStudio.Core
|
||||
{
|
||||
public sealed class ResourceLocation
|
||||
{
|
||||
private static List<ResourceLocation> ResourceGroups = new List<ResourceLocation>();
|
||||
private static readonly ResourceLocation Unknown = new ResourceLocation(string.Empty, ResourceCategory.Unknown, -1);
|
||||
|
||||
private static readonly Dictionary<string, ResourceLocation> _categoryLookUp = new Dictionary<string, ResourceLocation>()
|
||||
{
|
||||
["textures/items"] = new ResourceLocation("textures/items", ResourceCategory.ItemAnimation, 16, isGroup: true),
|
||||
["textures/blocks"] = new ResourceLocation("textures/blocks", ResourceCategory.BlockAnimation, 16, isGroup: true),
|
||||
["mob"] = new ResourceLocation("mob", ResourceCategory.MobEntityTextures, 1, isGroup: true),
|
||||
["item"] = new ResourceLocation("item", ResourceCategory.ItemEntityTextures, 1, isGroup: true),
|
||||
["terrain.png"] = new ResourceLocation("terrain.png", ResourceCategory.BlockAtlas, 16),
|
||||
["items.png"] = new ResourceLocation("items.png", ResourceCategory.ItemAtlas, 16),
|
||||
["particles.png"] = new ResourceLocation("particles.png", ResourceCategory.ParticleAtlas, 16),
|
||||
["item/banner/Banner_Atlas.png"] = new ResourceLocation("item/banner/Banner_Atlas.png", ResourceCategory.BannerAtlas, new Size(6, 7), TillingMode.WidthAndHeight),
|
||||
["art/kz.png"] = new ResourceLocation("art/kz.png", ResourceCategory.PaintingAtlas, 16),
|
||||
["misc/explosion.png"] = new ResourceLocation("misc/explosion.png", ResourceCategory.ExplosionAtlas, 4),
|
||||
["item/xporb.png"] = new ResourceLocation("item/xporb.png", ResourceCategory.ExperienceOrbAtlas, 4),
|
||||
["terrain/moon_phases.png"] = new ResourceLocation("terrain/moon_phases.png", ResourceCategory.MoonPhaseAtlas, 4),
|
||||
["misc/mapicons.png"] = new ResourceLocation("misc/mapicons.png", ResourceCategory.MapIconAtlas, 4),
|
||||
["misc/additionalmapicons.png"] = new ResourceLocation("misc/additionalmapicons.png", ResourceCategory.AdditionalMapIconsAtlas, 4),
|
||||
};
|
||||
|
||||
public static string GetPathFromCategory(ResourceCategory category)
|
||||
{
|
||||
return category switch
|
||||
{
|
||||
ResourceCategory.ItemAnimation => _categoryLookUp["textures/items"].ToString(),
|
||||
ResourceCategory.BlockAnimation => _categoryLookUp["textures/blocks"].ToString(),
|
||||
ResourceCategory.MobEntityTextures => _categoryLookUp["mob"].ToString(),
|
||||
ResourceCategory.ItemEntityTextures => _categoryLookUp["item"].ToString(),
|
||||
ResourceCategory.BlockAtlas => _categoryLookUp["terrain.png"].ToString(),
|
||||
ResourceCategory.ItemAtlas => _categoryLookUp["items.png"].ToString(),
|
||||
ResourceCategory.ParticleAtlas => _categoryLookUp["particles.png"].ToString(),
|
||||
ResourceCategory.BannerAtlas => _categoryLookUp["item/banner/Banner_Atlas.png"].ToString(),
|
||||
ResourceCategory.PaintingAtlas => _categoryLookUp["art/kz.png"].ToString(),
|
||||
ResourceCategory.ExplosionAtlas => _categoryLookUp["misc/explosion.png"].ToString(),
|
||||
ResourceCategory.ExperienceOrbAtlas => _categoryLookUp["item/xporb.png"].ToString(),
|
||||
ResourceCategory.MoonPhaseAtlas => _categoryLookUp["terrain/moon_phases.png"].ToString(),
|
||||
ResourceCategory.MapIconAtlas => _categoryLookUp["misc/mapicons.png"].ToString(),
|
||||
ResourceCategory.AdditionalMapIconsAtlas => _categoryLookUp["misc/additionalmapicons.png"].ToString(),
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public static ResourceCategory GetCategoryFromPath(string path) => GetFromPath(path).Category;
|
||||
|
||||
public static ResourceLocation GetFromPath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path) || !path.StartsWith("res/"))
|
||||
return Unknown;
|
||||
string categoryPath = path.Substring("res/".Length);
|
||||
if (_categoryLookUp.ContainsKey(categoryPath))
|
||||
return _categoryLookUp[categoryPath];
|
||||
return ResourceGroups.Where(group => categoryPath.StartsWith(group.Path)).FirstOrDefault() ?? Unknown;
|
||||
}
|
||||
|
||||
public enum TillingMode
|
||||
{
|
||||
Width,
|
||||
Height,
|
||||
WidthAndHeight
|
||||
}
|
||||
|
||||
public readonly string Path;
|
||||
public readonly ResourceCategory Category;
|
||||
public readonly Size TillingFactor;
|
||||
public readonly TillingMode Tilling;
|
||||
public readonly bool IsGroup;
|
||||
|
||||
public Size GetTileArea(Size imgSize)
|
||||
{
|
||||
int tileFactorWidth = Math.Max(1, TillingFactor.Width);
|
||||
int tileFactorHeight = Math.Max(1, TillingFactor.Height);
|
||||
return Tilling switch
|
||||
{
|
||||
TillingMode.Width => new Size(imgSize.Width / tileFactorWidth, imgSize.Width / tileFactorHeight),
|
||||
TillingMode.Height => new Size(imgSize.Height / tileFactorWidth, imgSize.Height / tileFactorHeight),
|
||||
TillingMode.WidthAndHeight => new Size(imgSize.Width / tileFactorWidth, imgSize.Height / tileFactorHeight),
|
||||
_ => Size.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
private ResourceLocation(string path, ResourceCategory category, int tillingFactor, TillingMode tilling = TillingMode.Width, bool isGroup = false)
|
||||
: this(path, category, new Size(tillingFactor, tillingFactor), tilling, isGroup)
|
||||
{
|
||||
}
|
||||
|
||||
private ResourceLocation(string path, ResourceCategory category, Size tillingFactor, TillingMode tilling = TillingMode.Width, bool isGroup = false)
|
||||
{
|
||||
Path = path;
|
||||
Category = category;
|
||||
TillingFactor = tillingFactor;
|
||||
Tilling = tilling;
|
||||
IsGroup = isGroup;
|
||||
if (isGroup)
|
||||
ResourceGroups.Add(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "res/" + Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
PckStudio.Core/Serializer/AnimationSerializer.cs
Normal file
81
PckStudio.Core/Serializer/AnimationSerializer.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
/* Copyright (c) 2024-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* 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.Core.Extensions;
|
||||
using PckStudio.Interfaces;
|
||||
using PckStudio.Core;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace PckStudio.Core.Serializer
|
||||
{
|
||||
public sealed class AnimationSerializer : IPckAssetSerializer<Animation>
|
||||
{
|
||||
public static readonly AnimationSerializer DefaultSerializer = new AnimationSerializer();
|
||||
|
||||
public void Serialize(Animation animation, ref PckAsset asset)
|
||||
{
|
||||
string anim = SerializeAnim(animation);
|
||||
asset.SetProperty("ANIM", anim);
|
||||
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);
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
PckStudio.Core/Serializer/ImageSerializer.cs
Normal file
51
PckStudio.Core/Serializer/ImageSerializer.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
/* Copyright (c) 2024-present miku-666
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using OMI.Formats.Pck;
|
||||
using PckStudio.Core.IO.TGA;
|
||||
using PckStudio.Interfaces;
|
||||
|
||||
namespace PckStudio.Core.Serializer
|
||||
{
|
||||
internal sealed class ImageSerializer : IPckAssetSerializer<Image>
|
||||
{
|
||||
public static readonly ImageSerializer DefaultSerializer = new ImageSerializer();
|
||||
|
||||
public void Serialize(Image obj, ref PckAsset asset)
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
try
|
||||
{
|
||||
if (Path.GetExtension(asset.Filename) == ".tga")
|
||||
TGASerializer.SerializeToStream(stream, obj);
|
||||
else
|
||||
obj.Save(stream, ImageFormat.Png);
|
||||
asset.SetData(stream.ToArray());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.TraceError($"Failed to serialize image to pck file data({asset.Filename}).");
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
PckStudio.Core/Skin/Skin.cs
Normal file
53
PckStudio.Core/Skin/Skin.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PckStudio.Core.Skin
|
||||
{
|
||||
public sealed class Skin
|
||||
{
|
||||
public SkinMetaData MetaData { get; set; }
|
||||
|
||||
public SkinIdentifier Identifier { get; set; }
|
||||
|
||||
public SkinANIM Anim { get; set; }
|
||||
|
||||
public SkinModel Model { get; set; }
|
||||
|
||||
public Image Texture { get; set; }
|
||||
|
||||
public Image CapeTexture { get; set; }
|
||||
|
||||
public bool HasCape => CapeTexture is not null;
|
||||
|
||||
public Skin(string name, Image texture)
|
||||
{
|
||||
MetaData = new SkinMetaData(name, string.Empty);
|
||||
Texture = texture;
|
||||
Model = new SkinModel();
|
||||
}
|
||||
|
||||
public Skin(string name, Image texture, Image capeTexture)
|
||||
: this(name, texture)
|
||||
{
|
||||
CapeTexture = capeTexture;
|
||||
}
|
||||
|
||||
public Skin(string name, SkinANIM anim, Image texture, IEnumerable<SkinBOX> additionalBoxes, IEnumerable<SkinPartOffset> partOffsets)
|
||||
: this(name, texture)
|
||||
{
|
||||
Model.AdditionalBoxes.AddRange(additionalBoxes);
|
||||
Model.PartOffsets.AddRange(partOffsets);
|
||||
Anim = anim;
|
||||
}
|
||||
|
||||
public Skin(string name, int id, Image texture, SkinANIM anim, IEnumerable<SkinBOX> additionalBoxes, IEnumerable<SkinPartOffset> partOffsets)
|
||||
: this(name, anim, texture, additionalBoxes, partOffsets)
|
||||
{
|
||||
Identifier = new(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
115
PckStudio.Core/Skin/SkinANIM.cs
Normal file
115
PckStudio.Core/Skin/SkinANIM.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
/* Copyright (c) 2022-present miku-666, MattNL
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PckStudio.Core.Skin
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Skin Anim value where flags can be set
|
||||
/// </summary>
|
||||
public class SkinANIM : ICloneable, IEquatable<SkinANIM>, IEquatable<SkinAnimMask>
|
||||
{
|
||||
public static readonly SkinANIM Empty = new SkinANIM(0);
|
||||
|
||||
private BitVector32 _flags;
|
||||
private static readonly Regex _validator = new Regex(@"^0x[0-9a-f]{1,8}\b", RegexOptions.IgnoreCase);
|
||||
|
||||
public SkinANIM(SkinAnimMask mask)
|
||||
: this((int)mask)
|
||||
{
|
||||
}
|
||||
|
||||
private SkinANIM(int mask)
|
||||
{
|
||||
_flags = new BitVector32(mask);
|
||||
}
|
||||
|
||||
public override string ToString() => "0x" + _flags.Data.ToString("x8");
|
||||
|
||||
public static bool IsValidANIM(string anim)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(anim) && _validator.IsMatch(anim);
|
||||
}
|
||||
|
||||
public static SkinANIM FromString(string value)
|
||||
=> IsValidANIM(value)
|
||||
? new SkinANIM(Convert.ToInt32(value.TrimEnd(' ', '\n', '\r'), 16))
|
||||
: Empty;
|
||||
|
||||
public static SkinANIM operator |(SkinANIM @this, SkinANIM other) => new SkinANIM(@this._flags.Data | other._flags.Data);
|
||||
|
||||
public static SkinANIM operator |(SkinANIM @this, SkinAnimMask mask) => new SkinANIM(@this._flags.Data | (int)mask);
|
||||
public static SkinANIM operator &(SkinANIM @this, SkinAnimMask mask) => new SkinANIM(@this._flags.Data & (int)mask);
|
||||
|
||||
public static SkinANIM FromValue(int value) => new SkinANIM(value);
|
||||
|
||||
public int ToValue() => _flags.Data;
|
||||
|
||||
public static implicit operator SkinANIM(SkinAnimMask mask) => new SkinANIM(mask);
|
||||
|
||||
public static bool operator ==(SkinANIM @this, SkinAnimMask mask) => @this.Equals(mask);
|
||||
public static bool operator !=(SkinANIM @this, SkinAnimMask mask) => !@this.Equals(mask);
|
||||
public static bool operator ==(SkinANIM @this, SkinANIM other) => @this.Equals(other);
|
||||
public static bool operator !=(SkinANIM @this, SkinANIM other) => !@this.Equals(other);
|
||||
|
||||
public bool Equals(SkinANIM other)
|
||||
{
|
||||
return _flags.Data == other._flags.Data;
|
||||
}
|
||||
|
||||
public bool Equals(SkinAnimMask other)
|
||||
{
|
||||
return _flags.Data == (int)other;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => obj is SkinANIM a && Equals(a);
|
||||
|
||||
public override int GetHashCode() => _flags.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the desired flag in the bitfield
|
||||
/// </summary>
|
||||
/// <param name="flag">ANIM Flag to set</param>
|
||||
/// <param name="state">State of the flag</param>
|
||||
public SkinANIM SetFlag(SkinAnimFlag flag, bool state)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(SkinAnimFlag), flag))
|
||||
throw new ArgumentOutOfRangeException(nameof(flag));
|
||||
return new SkinANIM(state ? _flags.Data | 1 << (int)flag : _flags.Data & ~(1 << (int)flag));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets flag state
|
||||
/// </summary>
|
||||
/// <param name="flag">Flag to check</param>
|
||||
/// <returns>True if flag is set, otherwise false</returns>
|
||||
public bool GetFlag(SkinAnimFlag flag)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(SkinAnimFlag), flag))
|
||||
throw new ArgumentOutOfRangeException(nameof(flag));
|
||||
return _flags[1 << (int)flag];
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return MemberwiseClone();
|
||||
}
|
||||
}
|
||||
}
|
||||
66
PckStudio.Core/Skin/SkinAnimFlag.cs
Normal file
66
PckStudio.Core/Skin/SkinAnimFlag.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
/* Copyright (c) 2022-present miku-666, MattNL
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
|
||||
namespace PckStudio.Core.Skin
|
||||
{
|
||||
/// <summary>
|
||||
/// For usage see <see cref="SkinANIM"/>
|
||||
/// </summary>
|
||||
public enum SkinAnimFlag : int
|
||||
{
|
||||
STATIC_ARMS = 0, // 0x01
|
||||
ZOMBIE_ARMS = 1, // 0x02
|
||||
STATIC_LEGS = 2, // 0x04
|
||||
BAD_SANTA = 3, // 0x08
|
||||
//
|
||||
__BIT_4 = 4, // 0x10 - Unused??
|
||||
SYNCED_LEGS = 5, // 0x20
|
||||
SYNCED_ARMS = 6, // 0x40
|
||||
STATUE_OF_LIBERTY = 7, // 0x80
|
||||
|
||||
ALL_ARMOR_DISABLED = 8, // 0x100
|
||||
HEAD_BOBBING_DISABLED = 9, // 0x200
|
||||
HEAD_DISABLED = 10, // 0x400
|
||||
RIGHT_ARM_DISABLED = 11, // 0x800
|
||||
|
||||
LEFT_ARM_DISABLED = 12, // 0x1000
|
||||
BODY_DISABLED = 13, // 0x2000
|
||||
RIGHT_LEG_DISABLED = 14, // 0x4000
|
||||
LEFT_LEG_DISABLED = 15, // 0x8000
|
||||
|
||||
HEAD_OVERLAY_DISABLED = 16, // 0x10000
|
||||
DO_BACKWARDS_CROUCH = 17, // 0x20000
|
||||
RESOLUTION_64x64 = 18, // 0x40000
|
||||
SLIM_MODEL = 19, // 0x80000
|
||||
|
||||
LEFT_ARM_OVERLAY_DISABLED = 20, // 0x100000
|
||||
RIGHT_ARM_OVERLAY_DISABLED = 21, // 0x200000
|
||||
LEFT_LEG_OVERLAY_DISABLED = 22, // 0x400000
|
||||
RIGHT_LEG_OVERLAY_DISABLED = 23, // 0x800000
|
||||
|
||||
BODY_OVERLAY_DISABLED = 24, // 0x1000000
|
||||
FORCE_HEAD_ARMOR = 25, // 0x2000000
|
||||
FORCE_RIGHT_ARM_ARMOR = 26, // 0x4000000
|
||||
FORCE_LEFT_ARM_ARMOR = 27, // 0x8000000
|
||||
|
||||
FORCE_BODY_ARMOR = 28, // 0x10000000
|
||||
FORCE_RIGHT_LEG_ARMOR = 29, // 0x20000000
|
||||
FORCE_LEFT_LEG_ARMOR = 30, // 0x40000000
|
||||
DINNERBONE = 31, // 0x80000000
|
||||
}
|
||||
}
|
||||
53
PckStudio.Core/Skin/SkinAnimMask.cs
Normal file
53
PckStudio.Core/Skin/SkinAnimMask.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PckStudio.Core.Skin
|
||||
{
|
||||
[Flags]
|
||||
public enum SkinAnimMask : int
|
||||
{
|
||||
NONE = 0, // 0x00
|
||||
STATIC_ARMS = 1 << 0, // 0x01
|
||||
ZOMBIE_ARMS = 1 << 1, // 0x02
|
||||
STATIC_LEGS = 1 << 2, // 0x04
|
||||
BAD_SANTA = 1 << 3, // 0x08
|
||||
|
||||
__BIT_4 = 1 << 4, // 0x10 - Unused??
|
||||
SYNCED_LEGS = 1 << 5, // 0x20
|
||||
SYNCED_ARMS = 1 << 6, // 0x40
|
||||
STATUE_OF_LIBERTY = 1 << 7, // 0x80
|
||||
|
||||
ALL_ARMOR_DISABLED = 1 << 8, // 0x100
|
||||
HEAD_BOBBING_DISABLED = 1 << 9, // 0x200
|
||||
HEAD_DISABLED = 1 << 10, // 0x400
|
||||
RIGHT_ARM_DISABLED = 1 << 11, // 0x800
|
||||
|
||||
LEFT_ARM_DISABLED = 1 << 12, // 0x1000
|
||||
BODY_DISABLED = 1 << 13, // 0x2000
|
||||
RIGHT_LEG_DISABLED = 1 << 14, // 0x4000
|
||||
LEFT_LEG_DISABLED = 1 << 15, // 0x8000
|
||||
|
||||
HEAD_OVERLAY_DISABLED = 1 << 16, // 0x10000
|
||||
DO_BACKWARDS_CROUCH = 1 << 17, // 0x20000
|
||||
RESOLUTION_64x64 = 1 << 18, // 0x40000
|
||||
SLIM_MODEL = 1 << 19, // 0x80000
|
||||
|
||||
LEFT_ARM_OVERLAY_DISABLED = 1 << 20, // 0x100000
|
||||
RIGHT_ARM_OVERLAY_DISABLED = 1 << 21, // 0x200000
|
||||
LEFT_LEG_OVERLAY_DISABLED = 1 << 22, // 0x400000
|
||||
RIGHT_LEG_OVERLAY_DISABLED = 1 << 23, // 0x800000
|
||||
|
||||
BODY_OVERLAY_DISABLED = 1 << 24, // 0x1000000
|
||||
FORCE_HEAD_ARMOR = 1 << 25, // 0x2000000
|
||||
FORCE_RIGHT_ARM_ARMOR = 1 << 26, // 0x4000000
|
||||
FORCE_LEFT_ARM_ARMOR = 1 << 27, // 0x8000000
|
||||
|
||||
FORCE_BODY_ARMOR = 1 << 28, // 0x10000000
|
||||
FORCE_RIGHT_LEG_ARMOR = 1 << 29, // 0x20000000
|
||||
FORCE_LEFT_LEG_ARMOR = 1 << 30, // 0x40000000
|
||||
DINNERBONE = 1 << 31, // 0x80000000
|
||||
}
|
||||
}
|
||||
180
PckStudio.Core/Skin/SkinBOX.cs
Normal file
180
PckStudio.Core/Skin/SkinBOX.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
/* Copyright (c) 2023-present miku-666, MattNL
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1.The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
**/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using PckStudio.Core.Extensions;
|
||||
|
||||
namespace PckStudio.Core.Skin
|
||||
{
|
||||
public record SkinBOX : IEquatable<SkinBOX>
|
||||
{
|
||||
public static readonly SkinBOX DefaultHead = new SkinBOX("HEAD", new Vector3(-4, -8, -4), new Vector3(8), Vector2.Zero);
|
||||
|
||||
public static readonly string[] BaseTypes = new string[]
|
||||
{
|
||||
"HEAD",
|
||||
"BODY",
|
||||
"ARM0",
|
||||
"ARM1",
|
||||
"LEG0",
|
||||
"LEG1",
|
||||
};
|
||||
|
||||
public static readonly string[] OverlayTypes = new string[]
|
||||
{
|
||||
"HEADWEAR",
|
||||
"JACKET",
|
||||
"SLEEVE0",
|
||||
"SLEEVE1",
|
||||
"PANTS0",
|
||||
"PANTS1",
|
||||
};
|
||||
|
||||
public static Dictionary<int, SkinAnimFlag> KnownHashes = new Dictionary<int, SkinAnimFlag>()
|
||||
{
|
||||
[unchecked((int)0x9560320c)] = SkinAnimFlag.HEAD_DISABLED, // HEAD -4 -8 -4 8 8 8 0 0 0 0 0
|
||||
|
||||
[unchecked((int)0x1f13e4a3)] = SkinAnimFlag.BODY_DISABLED, // BODY -4 0 -2 8 12 4 16 16 0 0 0
|
||||
|
||||
[unchecked((int)0x407c9b27)] = SkinAnimFlag.RIGHT_ARM_DISABLED, // ARM0 -3 -2 -2 4 12 4 40 16 0 0 0 // standard (64x64)
|
||||
[unchecked((int)0x867c9b27)] = SkinAnimFlag.RIGHT_ARM_DISABLED, // ARM0 -2 -2 -2 3 12 4 40 16 0 0 0 // slim
|
||||
|
||||
[unchecked((int)0xca3cf050)] = SkinAnimFlag.LEFT_ARM_DISABLED, // ARM1 -1 -2 -2 4 12 4 40 16 0 1 0 // classic (64x32)
|
||||
[unchecked((int)0x879b27)] = SkinAnimFlag.LEFT_ARM_DISABLED, // ARM1 -1 -2 -2 4 12 4 32 48 0 0 0 // standard (64x64)
|
||||
[unchecked((int)0xe8c79b27)] = SkinAnimFlag.LEFT_ARM_DISABLED, // ARM1 -1 -2 -2 3 12 4 32 48 0 0 0 // slim
|
||||
|
||||
[unchecked((int)0x1910e24a)] = SkinAnimFlag.LEFT_LEG_DISABLED, // LEG1 -2 0 -2 4 12 4 16 48 0 0 0 // 64x64
|
||||
[unchecked((int)0xce263773)] = SkinAnimFlag.LEFT_LEG_DISABLED, // LEG1 -2 0 -2 4 12 4 0 16 0 1 0 // 64x32
|
||||
|
||||
[unchecked((int)0x5da5e24a)] = SkinAnimFlag.RIGHT_LEG_DISABLED, // LEG0 -2 0 -2 4 12 4 0 16 0 0 0
|
||||
|
||||
[unchecked((int)0x4bfe0142)] = SkinAnimFlag.HEAD_OVERLAY_DISABLED, // HEADWEAR -4 -8 -4 8 8 8 32 0 0 0 0
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
[unchecked((int)0xe693e4a3)] = SkinAnimFlag.BODY_OVERLAY_DISABLED, // BODY -4 0 -2 8 12 4 16 32 0 0 0
|
||||
[unchecked((int)0x8e322609)] = SkinAnimFlag.BODY_OVERLAY_DISABLED, // JACKET -4 0 -2 8 12 4 16 32 0 0 0
|
||||
|
||||
[unchecked((int)0x860c4433)] = SkinAnimFlag.RIGHT_ARM_OVERLAY_DISABLED, // SLEEVE0 -3 -2 -2 4 12 4 40 32 0 0 0 // classic
|
||||
[unchecked((int)0xcc0c4433)] = SkinAnimFlag.RIGHT_ARM_OVERLAY_DISABLED, // SLEEVE0 -2 -2 -2 3 12 4 40 32 0 0 0 // slim
|
||||
|
||||
[unchecked((int)0x91407908)] = SkinAnimFlag.LEFT_ARM_OVERLAY_DISABLED, // SLEEVE1 -1 -2 -2 4 12 4 48 48 0 0 0 // classic
|
||||
[unchecked((int)0x79807908)] = SkinAnimFlag.LEFT_ARM_OVERLAY_DISABLED, // SLEEVE1 -1 -2 -2 3 12 4 48 48 0 0 0 // slim
|
||||
|
||||
[unchecked((int)0x4de0238a)] = SkinAnimFlag.RIGHT_LEG_OVERLAY_DISABLED, // PANTS0 -2 0 -2 4 12 4 0 32 0 0 0
|
||||
|
||||
[unchecked((int)0x176f238a)] = SkinAnimFlag.LEFT_LEG_OVERLAY_DISABLED, // PANTS1 -2 0 -2 4 12 4 0 48 0 0 0
|
||||
};
|
||||
|
||||
public static readonly string[] ValidBoxTypes = BaseTypes.Concat(OverlayTypes).ToArray();
|
||||
|
||||
public string Type { get; }
|
||||
public Vector3 Pos { get; }
|
||||
public Vector3 Size { get; }
|
||||
public Vector2 UV { get; }
|
||||
public bool HideWithArmor { get; }
|
||||
public bool Mirror { get; }
|
||||
public float Scale { get; }
|
||||
|
||||
public SkinBOX(string type, Vector3 pos, Vector3 size, Vector2 uv,
|
||||
bool hideWithArmor = false, bool mirror = false, float scale = 0.0f)
|
||||
{
|
||||
Type = type;
|
||||
Pos = pos;
|
||||
Size = size;
|
||||
UV = uv;
|
||||
HideWithArmor = hideWithArmor;
|
||||
Mirror = mirror;
|
||||
Scale = scale;
|
||||
}
|
||||
|
||||
public static SkinBOX FromString(string value)
|
||||
{
|
||||
var arguments = value.TrimEnd('\n', '\r', ' ').Split(' ');
|
||||
if (arguments.Length < 9)
|
||||
{
|
||||
throw new ArgumentException("Arguments must have at least a length of 9");
|
||||
}
|
||||
var type = arguments[0];
|
||||
Vector3 pos = TryGetVector3(arguments, 1);
|
||||
Vector3 size = TryGetVector3(arguments, 4);
|
||||
Vector2 uv = TryGetVector2(arguments, 7);
|
||||
|
||||
bool hideWithArmor = arguments.IndexInRange(9) && arguments[9] == "1";
|
||||
bool mirror = arguments.IndexInRange(10) && arguments[10] == "1";
|
||||
float scale = default;
|
||||
if (arguments.IndexInRange(11))
|
||||
float.TryParse(arguments[11], out scale);
|
||||
return new SkinBOX(type, pos, size, uv, hideWithArmor, mirror, scale);
|
||||
}
|
||||
|
||||
public bool IsValidType() => IsValidType(Type);
|
||||
|
||||
public static bool IsValidType(string type) => ValidBoxTypes.Contains(type);
|
||||
|
||||
public bool IsBasePart() => IsBasePart(Type);
|
||||
|
||||
public static bool IsBasePart(string type) => BaseTypes.Contains(type);
|
||||
|
||||
public bool IsOverlayPart() => IsOverlayPart(Type);
|
||||
|
||||
public static bool IsOverlayPart(string type) => OverlayTypes.Contains(type);
|
||||
|
||||
public KeyValuePair<string, string> ToProperty()
|
||||
{
|
||||
return new KeyValuePair<string, string>("BOX", ToString());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"{Type} {Pos.X} {Pos.Y} {Pos.Z} {Size.X} {Size.Y} {Size.Z} {UV.X} {UV.Y} {Convert.ToInt32(HideWithArmor)} {Convert.ToInt32(Mirror)} {Scale}"
|
||||
.Replace(',', '.');
|
||||
}
|
||||
|
||||
private static Vector2 TryGetVector2(string[] arguments, int startIndex)
|
||||
{
|
||||
float.TryParse(arguments[startIndex], out float x);
|
||||
float.TryParse(arguments[startIndex + 1], out float y);
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
private static Vector3 TryGetVector3(string[] arguments, int startIndex)
|
||||
{
|
||||
Vector2 xy = TryGetVector2(arguments, startIndex);
|
||||
float.TryParse(arguments[startIndex + 2], out float z);
|
||||
return new Vector3(xy, z);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hashCode = -1311939065;
|
||||
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Type);
|
||||
hashCode = hashCode * -1521134295 + Pos.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + Size.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + UV.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + HideWithArmor.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + Mirror.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + Scale.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
PckStudio.Core/Skin/SkinIdentifier.cs
Normal file
23
PckStudio.Core/Skin/SkinIdentifier.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace PckStudio.Core.Skin
|
||||
{
|
||||
public sealed class SkinIdentifier : IFormattable
|
||||
{
|
||||
public int Id { get; }
|
||||
|
||||
public SkinIdentifier(int id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public static implicit operator int(SkinIdentifier _this) => _this.Id;
|
||||
|
||||
public string ToString(string format, IFormatProvider formatProvider) => Id.ToString(format, formatProvider);
|
||||
|
||||
public string ToString(string format) => Id.ToString(format, NumberFormatInfo.CurrentInfo);
|
||||
|
||||
public override string ToString() => Id.ToString(NumberFormatInfo.CurrentInfo);
|
||||
}
|
||||
}
|
||||
14
PckStudio.Core/Skin/SkinMetaData.cs
Normal file
14
PckStudio.Core/Skin/SkinMetaData.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace PckStudio.Core.Skin
|
||||
{
|
||||
public sealed class SkinMetaData
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Theme { get; }
|
||||
|
||||
public SkinMetaData(string name, string theme)
|
||||
{
|
||||
Name = name;
|
||||
Theme = theme;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
PckStudio.Core/Skin/SkinModel.cs
Normal file
27
PckStudio.Core/Skin/SkinModel.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using OMI.Formats.Pck;
|
||||
|
||||
namespace PckStudio.Core.Skin
|
||||
{
|
||||
public sealed class SkinModel
|
||||
{
|
||||
public readonly List<SkinBOX> AdditionalBoxes;
|
||||
public readonly List<SkinPartOffset> PartOffsets;
|
||||
|
||||
public SkinModel()
|
||||
{
|
||||
AdditionalBoxes = new List<SkinBOX>();
|
||||
PartOffsets = new List<SkinPartOffset>(5);
|
||||
}
|
||||
|
||||
public SkinModel(IEnumerable<SkinBOX> additionalBoxes, IEnumerable<SkinPartOffset> partOffsets)
|
||||
{
|
||||
AdditionalBoxes = new List<SkinBOX>(additionalBoxes);
|
||||
PartOffsets = new List<SkinPartOffset>(partOffsets);
|
||||
}
|
||||
}
|
||||
}
|
||||
81
PckStudio.Core/Skin/SkinPartOffset.cs
Normal file
81
PckStudio.Core/Skin/SkinPartOffset.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using PckStudio.Core.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PckStudio.Core.Skin
|
||||
{
|
||||
public readonly struct SkinPartOffset
|
||||
{
|
||||
private static readonly Regex sWhitespace = new Regex(@"\s+");
|
||||
public static string ReplaceWhitespace(string input, string replacement)
|
||||
{
|
||||
return sWhitespace.Replace(input, replacement);
|
||||
}
|
||||
|
||||
public static readonly string[] ValidModelOffsetTypes = new string[]
|
||||
{
|
||||
//! See: 0x02af8a20 - 0x02af8ed8 (Wii U Editon)
|
||||
"HEAD",
|
||||
"BODY",
|
||||
"ARM0",
|
||||
"ARM1",
|
||||
"LEG0",
|
||||
"LEG1",
|
||||
|
||||
"TOOL0",
|
||||
"TOOL1",
|
||||
|
||||
"HELMET",
|
||||
"SHOULDER0",
|
||||
"SHOULDER1",
|
||||
"CHEST",
|
||||
"WAIST",
|
||||
"PANTS0",
|
||||
"PANTS1",
|
||||
"BOOT0",
|
||||
"BOOT1",
|
||||
};
|
||||
|
||||
public string Type { get; }
|
||||
public float Value { get; }
|
||||
|
||||
public SkinPartOffset(string type, float value)
|
||||
{
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static SkinPartOffset FromString(string offsetFormatString)
|
||||
{
|
||||
string[] offset = ReplaceWhitespace(offsetFormatString.TrimEnd('\n', '\r', ' '), ",").Split(',');
|
||||
if (offset.Length < 3)
|
||||
throw new InvalidDataException("Format string does not contain enough data.");
|
||||
|
||||
string type = offset[0];
|
||||
|
||||
if (!ValidModelOffsetTypes.Contains(type))
|
||||
{
|
||||
Debug.WriteLine($"'{type}' is an invalid offset type.", category: nameof(SkinPartOffset));
|
||||
}
|
||||
|
||||
// Ignore => Y assumed
|
||||
//if (offset[1] != "Y")
|
||||
|
||||
if (!float.TryParse(offset[2], out float value))
|
||||
{
|
||||
Debug.WriteLine($"Failed to parse y offset for: '{type}'", category: nameof(SkinPartOffset));
|
||||
}
|
||||
return new SkinPartOffset(type, value);
|
||||
}
|
||||
|
||||
public KeyValuePair<string, string> ToProperty()
|
||||
{
|
||||
string value = $"{Type} Y {Value}";
|
||||
return new KeyValuePair<string, string>("OFFSET", value.Replace(',', '.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user