Skip to content

Commit

Permalink
[AK][CLI] Add support for portrait sprites
Browse files Browse the repository at this point in the history
  • Loading branch information
aelurum committed Aug 24, 2023
1 parent 572e3bf commit 381a7d8
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 34 deletions.
2 changes: 1 addition & 1 deletion AssetStudio/AssetsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void SetAssetFilter(params ClassIDType[] classIDTypes)
{
filteredAssetTypesList.Add(ClassIDType.MonoScript);
}
if (classIDTypes.Contains(ClassIDType.Sprite))
if (classIDTypes.Contains(ClassIDType.Sprite) || classIDTypes.Contains(ClassIDType.AkPortraitSprite))
{
filteredAssetTypesList.UnionWith(new HashSet<ClassIDType>
{
Expand Down
98 changes: 79 additions & 19 deletions AssetStudioCLI/Components/Arknights/AkSpriteHelper.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using AssetStudio;
using Arknights.PortraitSpriteMono;
using AssetStudio;
using AssetStudioCLI;
using AssetStudioCLI.Options;
using Newtonsoft.Json;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Generic;
using System.IO;

namespace Arknights
Expand Down Expand Up @@ -83,6 +86,61 @@ public static Image<Bgra32> AkGetImage(this Sprite m_Sprite, AvgSprite avgSprite
return null;
}

public static Image<Bgra32> AkGetImage(this PortraitSprite portraitSprite, SpriteMaskMode spriteMaskMode = SpriteMaskMode.On)
{
if (portraitSprite.Texture != null && portraitSprite.AlphaTexture != null)
{
var tex = CutImage(portraitSprite.Texture.ConvertToImage(false), portraitSprite.TextureRect, portraitSprite.DownscaleMultiplier, portraitSprite.Rotate);

if (spriteMaskMode == SpriteMaskMode.Off)
{
return tex;
}
else
{
var alphaTex = CutImage(portraitSprite.AlphaTexture.ConvertToImage(false), portraitSprite.TextureRect, portraitSprite.DownscaleMultiplier, portraitSprite.Rotate);
tex.ApplyRGBMask(alphaTex);
return tex;
}
}

return null;
}

public static List<PortraitSprite> GeneratePortraits(AssetItem asset)
{
var portraits = new List<PortraitSprite>();

var portraitsDict = ((MonoBehaviour)asset.Asset).ToType();
if (portraitsDict == null)
{
Logger.Warning("Portraits MonoBehaviour is not readable.");
return portraits;
}
var portraitsJson = JsonConvert.SerializeObject(portraitsDict);
var portraitsData = JsonConvert.DeserializeObject<PortraitSpriteConfig>(portraitsJson);

var atlasTex = (Texture2D)Studio.loadedAssetsList.Find(x => x.m_PathID == portraitsData._atlas.Texture.m_PathID).Asset;
var atlasAlpha = (Texture2D)Studio.loadedAssetsList.Find(x => x.m_PathID == portraitsData._atlas.Alpha.m_PathID).Asset;

foreach (var portraitData in portraitsData._sprites)
{
var portraitSprite = new PortraitSprite()
{
Name = portraitData.Name,
AssetsFile = atlasTex.assetsFile,
Container = asset.Container,
Texture = atlasTex,
AlphaTexture = atlasAlpha,
TextureRect = new Rectf(portraitData.Rect.X, portraitData.Rect.Y, portraitData.Rect.W, portraitData.Rect.H),
Rotate = portraitData.Rotate,
};
portraits.Add(portraitSprite);
}

return portraits;
}

private static void ApplyRGBMask(this Image<Bgra32> tex, Image<Bgra32> texMask)
{
using (texMask)
Expand Down Expand Up @@ -120,29 +178,31 @@ private static void ApplyRGBMask(this Image<Bgra32> tex, Image<Bgra32> texMask)
}
}

private static Image<Bgra32> CutImage(Image<Bgra32> originalImage, Rectf textureRect, float downscaleMultiplier)
private static Image<Bgra32> CutImage(Image<Bgra32> originalImage, Rectf textureRect, float downscaleMultiplier, bool rotate = false)
{
if (originalImage != null)
{
using (originalImage)
if (downscaleMultiplier > 0f && downscaleMultiplier != 1f)
{
if (downscaleMultiplier > 0f && downscaleMultiplier != 1f)
{
var newSize = (Size)(originalImage.Size() / downscaleMultiplier);
originalImage.Mutate(x => x.Resize(newSize, KnownResamplers.Lanczos3, compand: true));
}
var rectX = (int)Math.Floor(textureRect.x);
var rectY = (int)Math.Floor(textureRect.y);
var rectRight = (int)Math.Ceiling(textureRect.x + textureRect.width);
var rectBottom = (int)Math.Ceiling(textureRect.y + textureRect.height);
rectRight = Math.Min(rectRight, originalImage.Width);
rectBottom = Math.Min(rectBottom, originalImage.Height);
var rect = new Rectangle(rectX, rectY, rectRight - rectX, rectBottom - rectY);
var spriteImage = originalImage.Clone(x => x.Crop(rect));
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));

return spriteImage;
var newSize = (Size)(originalImage.Size() / downscaleMultiplier);
originalImage.Mutate(x => x.Resize(newSize, KnownResamplers.Lanczos3, compand: true));
}
var rectX = (int)Math.Floor(textureRect.x);
var rectY = (int)Math.Floor(textureRect.y);
var rectRight = (int)Math.Ceiling(textureRect.x + textureRect.width);
var rectBottom = (int)Math.Ceiling(textureRect.y + textureRect.height);
rectRight = Math.Min(rectRight, originalImage.Width);
rectBottom = Math.Min(rectBottom, originalImage.Height);
var rect = new Rectangle(rectX, rectY, rectRight - rectX, rectBottom - rectY);
var spriteImage = originalImage.Clone(x => x.Crop(rect));
originalImage.Dispose();
if (rotate)
{
spriteImage.Mutate(x => x.Rotate(RotateMode.Rotate270));
}
spriteImage.Mutate(x => x.Flip(FlipMode.Vertical));

return spriteImage;
}

return null;
Expand Down
2 changes: 1 addition & 1 deletion AssetStudioCLI/Components/Arknights/AvgSprite.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Arknights.AvgCharHub;
using Arknights.AvgCharHubMono;
using AssetStudio;
using AssetStudioCLI;
using SixLabors.ImageSharp;
Expand Down
2 changes: 1 addition & 1 deletion AssetStudioCLI/Components/Arknights/AvgSpriteConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using AssetStudio;

namespace Arknights.AvgCharHub
namespace Arknights.AvgCharHubMono
{
internal class AvgAssetIDs
{
Expand Down
24 changes: 24 additions & 0 deletions AssetStudioCLI/Components/Arknights/PortraitSprite.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using AssetStudio;


namespace Arknights
{
internal class PortraitSprite
{
public string Name { get; set; }
public ClassIDType Type { get; }
public SerializedFile AssetsFile { get; set; }
public string Container { get; set; }
public Texture2D Texture { get; set; }
public Texture2D AlphaTexture { get; set; }
public Rectf TextureRect { get; set; }
public bool Rotate { get; set; }
public float DownscaleMultiplier { get; }

public PortraitSprite()
{
Type = ClassIDType.AkPortraitSprite;
DownscaleMultiplier = 1f;
}
}
}
41 changes: 41 additions & 0 deletions AssetStudioCLI/Components/Arknights/PortraitSpriteConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Arknights.PortraitSpriteMono
{
internal class PortraitRect
{
public float X { get; set; }
public float Y { get; set; }
public float W { get; set; }
public float H { get; set; }
}

internal class AtlasSprite
{
public string Name { get; set; }
public string Guid { get; set; }
public int Atlas { get; set; }
public PortraitRect Rect { get; set; }
public bool Rotate { get; set; }
}

internal class TextureIDs
{
public int m_FileID { get; set; }
public long m_PathID { get; set; }
}

internal class AtlasInfo
{
public int Index { get; set; }
public TextureIDs Texture { get; set; }
public TextureIDs Alpha { get; set; }
public int Size { get; set; }
}

internal class PortraitSpriteConfig
{
public string m_Name { get; set; }
public AtlasSprite[] _sprites { get; set; }
public AtlasInfo _atlas { get; set; }
public int _index { get; set; }
}
}
16 changes: 15 additions & 1 deletion AssetStudioCLI/Components/AssetItem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AssetStudio;
using Arknights;
using AssetStudio;

namespace AssetStudioCLI
{
Expand All @@ -13,6 +14,7 @@ internal class AssetItem
public ClassIDType Type;
public string Text;
public string UniqueID;
public PortraitSprite AkPortraitSprite;

public AssetItem(Object asset)
{
Expand All @@ -23,5 +25,17 @@ public AssetItem(Object asset)
m_PathID = asset.m_PathID;
FullSize = asset.byteSize;
}

public AssetItem(PortraitSprite akPortraitSprite)
{
Asset = null;
SourceFile = akPortraitSprite.AssetsFile;
Container = akPortraitSprite.Container;
Type = akPortraitSprite.Type;
TypeString = Type.ToString();
Text = akPortraitSprite.Name;
m_PathID = -1;
AkPortraitSprite = akPortraitSprite;
}
}
}
51 changes: 44 additions & 7 deletions AssetStudioCLI/Exporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace AssetStudioCLI
{
Expand Down Expand Up @@ -224,15 +225,16 @@ public static bool ExportSprite(AssetItem item, string exportPath)
{
alias = $"_{avgSprite.Alias}";
}
}

if (!CLIOptions.f_akOriginalAvgNames.Value)
{
if ((m_Sprite.m_Name.Length < 3 && m_Sprite.m_Name.All(char.IsDigit)) //not grouped ("spriteIndex")
|| (m_Sprite.m_Name.Length < 5 && m_Sprite.m_Name.Contains('$') && m_Sprite.m_Name.Split('$')[0].All(char.IsDigit))) //grouped ("spriteIndex$groupIndex")
if (!CLIOptions.f_akOriginalAvgNames.Value)
{
var fullName = Path.GetFileNameWithoutExtension(item.Container);
item.Text = $"{fullName}#{m_Sprite.m_Name}";
var groupedPattern = new Regex(@"^\d{1,2}\$\d{1,2}$"); // "spriteIndex$groupIndex"
var notGroupedPattern = new Regex(@"^\d{1,2}$"); // "spriteIndex"
if (groupedPattern.IsMatch(m_Sprite.m_Name) || notGroupedPattern.IsMatch(m_Sprite.m_Name))
{
var fullName = Path.GetFileNameWithoutExtension(item.Container);
item.Text = $"{fullName}#{m_Sprite.m_Name}";
}
}
}

Expand Down Expand Up @@ -272,8 +274,36 @@ public static bool ExportSprite(AssetItem item, string exportPath)
return false;
}

public static bool ExportPortraitSprite(AssetItem item, string exportPath)
{
var type = CLIOptions.o_imageFormat.Value;
var spriteMaskMode = CLIOptions.o_akSpriteMaskMode.Value != AkSpriteMaskMode.None ? SpriteMaskMode.Export : SpriteMaskMode.Off;
if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath))
return false;

var image = item.AkPortraitSprite.AkGetImage(spriteMaskMode: spriteMaskMode);
if (image != null)
{
using (image)
{
using (var file = File.OpenWrite(exportFullPath))
{
image.WriteToStream(file, type);
}
Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\"");
return true;
}
}
return false;
}

public static bool ExportRawFile(AssetItem item, string exportPath)
{
if (item.Asset == null)
{
Logger.Warning($"Raw export is not supported for \"{item.Text}\" ({item.TypeString}) file");
return false;
}
if (!TryExportFile(exportPath, item, ".dat", out var exportFullPath))
return false;
File.WriteAllBytes(exportFullPath, item.Asset.GetRawData());
Expand All @@ -284,6 +314,11 @@ public static bool ExportRawFile(AssetItem item, string exportPath)

public static bool ExportDumpFile(AssetItem item, string exportPath)
{
if (item.Asset == null)
{
Logger.Warning($"Dump is not supported for \"{item.Text}\" ({item.TypeString}) file");
return false;
}
if (!TryExportFile(exportPath, item, ".txt", out var exportFullPath))
return false;
var str = item.Asset.Dump();
Expand Down Expand Up @@ -439,6 +474,8 @@ public static bool ExportConvertFile(AssetItem item, string exportPath)
return ExportFont(item, exportPath);
case ClassIDType.Sprite:
return ExportSprite(item, exportPath);
case ClassIDType.AkPortraitSprite:
return ExportPortraitSprite(item, exportPath);
case ClassIDType.Mesh:
return ExportMesh(item, exportPath);
default:
Expand Down
12 changes: 8 additions & 4 deletions AssetStudioCLI/Options/CLIOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ private static void InitOptions()
{
ClassIDType.Texture2D,
ClassIDType.Sprite,
ClassIDType.AkPortraitSprite,
ClassIDType.TextAsset,
ClassIDType.MonoBehaviour,
ClassIDType.Font,
Expand Down Expand Up @@ -184,8 +185,8 @@ private static void InitOptions()
optionDefaultValue: supportedAssetTypes,
optionName: "-t, --asset-type <value(s)>",
optionDescription: "Specify asset type(s) to export\n" +
"<Value(s): tex2d, sprite, textAsset, monoBehaviour, font, shader, movieTexture,\n" +
"audio, video, mesh | all(default)>\n" +
"<Value(s): tex2d, sprite, akPortrait, textAsset, monoBehaviour, font, shader,\n" +
"movieTexture, audio, video, mesh | all(default)>\n" +
"All - export all asset types, which are listed in the values\n" +
"*To specify multiple asset types, write them separated by ',' or ';' without spaces\n" +
"Examples: \"-t sprite\" or \"-t tex2d,sprite,audio\" or \"-t tex2d;sprite;font\"\n",
Expand Down Expand Up @@ -532,6 +533,9 @@ public static void ParseArgs(string[] args)
case "sprite":
o_exportAssetTypes.Value.Add(ClassIDType.Sprite);
break;
case "akportrait":
o_exportAssetTypes.Value.Add(ClassIDType.AkPortraitSprite);
break;
case "textasset":
o_exportAssetTypes.Value.Add(ClassIDType.TextAsset);
break;
Expand Down Expand Up @@ -959,10 +963,10 @@ public static void ShowCurrentOptions()
sb.AppendLine($"# Asset Group Option: {o_groupAssetsBy}");
sb.AppendLine($"# Export Image Format: {o_imageFormat}");
sb.AppendLine($"# Export Audio Format: {o_audioFormat}");
sb.AppendLine($"# [Arkingths] Sprite Mode: {o_akSpriteMaskMode}");
sb.AppendLine($"# [Arkingths] Sprite Mask Mode: {o_akSpriteMaskMode}");
sb.AppendLine($"# [Arknights] Mask Resampler: {resamplerName}");
sb.AppendLine($"# [Arknights] Mask Gamma Correction: {o_akAlphaMaskGamma.Value * 10:+#;-#;0}%");
sb.AppendLine($"# [Arknights] Original Avg Names: {f_akOriginalAvgNames}");
sb.AppendLine($"# [Arknights] Don't Fix Avg Names: {f_akOriginalAvgNames}");
sb.AppendLine($"# [Arknights] Add Aliases: {f_akAddAliases}");
sb.AppendLine($"# Log Level: {o_logLevel}");
sb.AppendLine($"# Log Output: {o_logOutput}");
Expand Down
9 changes: 9 additions & 0 deletions AssetStudioCLI/Studio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ public static void ParseAssets()
if (containers.ContainsKey(asset.Asset))
{
asset.Container = containers[asset.Asset];

if (asset.Type == ClassIDType.MonoBehaviour && asset.Container.Contains("/arts/charportraits/portraits"))
{
var portraitsList = Arknights.AkSpriteHelper.GeneratePortraits(asset);
foreach (var portrait in portraitsList)
{
exportableAssetsList.Add(new AssetItem(portrait));
}
}
}
}
if (CLIOptions.o_workMode.Value != WorkMode.ExportLive2D)
Expand Down

0 comments on commit 381a7d8

Please sign in to comment.