Compare commits

..

No commits in common. "00e8bcc038f0690cb3c87aaeaddee1cbc6176f98" and "3848c7c446f9e533cb805bd259abe9ae55de7af8" have entirely different histories.

4 changed files with 132 additions and 320 deletions

View File

@ -8,8 +8,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.0.0" /> <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.10.0" />
<PackageReference Include="Magick.NET.Core" Version="14.0.0" /> <PackageReference Include="Magick.NET.Core" Version="13.10.0" />
<PackageReference Include="NLua" Version="1.7.3" /> <PackageReference Include="NLua" Version="1.7.3" />
<PackageReference Include="System.Threading.Tasks" Version="4.3.0" /> <PackageReference Include="System.Threading.Tasks" Version="4.3.0" />
</ItemGroup> </ItemGroup>

View File

@ -1,124 +0,0 @@
using System.Reflection;
using System.Xml.Linq;
namespace ArtemisFgTools
{
public class FgHelper
{
public class FgObject(string path, string head, List<string> fuku, List<List<string>> pose, Dictionary<string, List<string>> face)
{
public string Path { get; set; } = path;
public string Head { get; set; } = head;
public List<string> Fuku { get; set; } = fuku;
public List<List<string>> Pose { get; set; } = pose;
public Dictionary<string, List<string>> Face { get; set; } = face;
}
//{"fg",ch="零",size="z1",mx=40,mode=3,resize=1,path=":fg/rei/z1/",file="rei_z1a0200",face="a0001",head="rei_z1a",lv=4,id=11},
public class FgRecord : IEquatable<FgRecord>
{
public string ChName { get; set; }
public string Size { get; set; }
public string File { get; set; }
public string Face { get; set; }
public override bool Equals(object? obj)
{
return Equals(obj as FgRecord);
}
public bool Equals(FgRecord? other)
{
return other != null &&
ChName == other.ChName &&
Size == other.Size &&
File == other.File &&
Face == other.Face;
}
public override int GetHashCode()
{
return HashCode.Combine(ChName, Size, File, Face);
}
}
public static HashSet<FgRecord> FetchFgObjectsFromScript(string path)
{
if (!Directory.Exists(path))
throw new DirectoryNotFoundException("The path does not exist.");
else
{
List<string> fgScriptLine = [];
foreach (string f in Directory.GetFiles(path, "*.ast"))
{
using StreamReader sr = new(f);
string? line;
while ((line = sr.ReadLine()) != null)
{
if (line.Contains("{\"fg\","))
fgScriptLine.Add(line);
}
}
if (fgScriptLine.Count == 0)
throw new Exception("No valid fg script line found.");
else
{
HashSet<FgRecord> fgRecords = [];
foreach (var line in fgScriptLine)
{
FgRecord? fgRecord = ParseScriptFGLine(line);
if (fgRecord != null)
fgRecords.Add(fgRecord);
}
if (fgRecords.Count == 0)
throw new Exception("No valid fg object found.");
else
return fgRecords;
}
}
}
public static FgRecord? ParseScriptFGLine(string input)
{
FgRecord fgRecord = new();
input = input.Trim('{', '}');
var pairs = input.Split(',');
foreach (var pair in pairs)
{
var keyValue = pair.Split(['=', '='], 2);
if (keyValue.Length == 2)
{
string key = keyValue[0].Trim();
string value = keyValue[1].Trim().Trim('"').Trim('}');
PropertyInfo? property = typeof(FgRecord).GetProperty(char.ToUpper(key[0]) + key[1..]);
if (property != null)
{
if (property.PropertyType == typeof(int))
{
if (int.TryParse(value, out int intValue))
property.SetValue(fgRecord, intValue);
else
throw new Exception($"Invalid integer value '{value}' for property '{key}'.");
}
else if (property.PropertyType == typeof(string))
{
property.SetValue(fgRecord, value);
}
// 其他类型的处理可以在这里添加
}
}
}
//补个chname
if (fgRecord.File != null)
fgRecord.ChName = GetCharacterEngName(fgRecord.File);
return fgRecord.Size == null ? null : fgRecord;
}
public static string GetCharacterEngName(string input)
{
int underscoreIndex = input.IndexOf('_');
if (underscoreIndex > 0)
return input[..underscoreIndex];
throw new Exception("Not supported character name format.");
}
}
}

View File

@ -1,220 +1,162 @@
using ImageMagick; using ImageMagick;
using NLua; using NLua;
using System.Reflection.Metadata.Ecma335;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using static ArtemisFgTools.FgHelper;
namespace ArtemisFgTools namespace ArtemisFgTools
{ {
internal class Program internal class Program
{ {
static void Main(string[] args) public class FgObject(string path, string head, List<string> fuku, List<List<string>> pose, Dictionary<string, List<string>> face)
{ {
if (args.Length == 1) public string Path { get; set; } = path;
public string Head { get; set; } = head;
public List<string> Fuku { get; set; } = fuku;
public List<List<string>> Pose { get; set; } = pose;
public Dictionary<string, List<string>> Face { get; set; } = face;
}
static void Main()
{
Console.WriteLine("请输入立绘fg文件夹的所在路径无需\"\"");
string? fgImagePath = Console.ReadLine();
Console.WriteLine("请输入exlist的文件路径");
string? luaFilePath = Console.ReadLine();
Console.WriteLine("请输入保存位置:");
string? savePath = Console.ReadLine();
if (string.IsNullOrEmpty(fgImagePath) || string.IsNullOrEmpty(luaFilePath) || string.IsNullOrEmpty(savePath))
{ {
if (args[0] == "-h") Console.WriteLine("路径不能为空");
Console.WriteLine("Usage: tools.exe -c <fgPath> (-s <scriptPath> | -t <luaTablePath>) -o <outputPath>"); return;
else
Console.WriteLine("Invalid arguments, Please check the usage via -h");
} }
else if (args.Length != 6) if (!Directory.Exists(fgImagePath) || !File.Exists(luaFilePath))
Console.WriteLine("Invalid arguments, Please check the usage via -h");
else if (args[0] != "-c" || !(args[2] == "-s" || args[2] == "-t") || args[4] != "-o")
Console.WriteLine("Invalid arguments, Please check the usage via -h");
else
{ {
if (!Directory.Exists(args[5])) Console.WriteLine("路径不存在");
Directory.CreateDirectory(args[5]); return;
if (!Directory.Exists(args[1])) }
Console.WriteLine("Invalid fg path"); if (!Directory.Exists(savePath))
else if (args[2] == "-s") {
{ Directory.CreateDirectory(savePath);
if (!Directory.Exists(args[3]))
Console.WriteLine("Invalid script path");
else
PreProcess2(args[1], args[5], args[3]);
}
else
{
if (!File.Exists(args[3]))
Console.WriteLine("Invalid lua table path");
else
PreProcess(args[1], args[5], args[3]);
}
} }
Console.WriteLine("Press any key to exit..."); Dictionary<object, object>? dictionary = ParseLuaTable(luaFilePath);
Console.ReadKey();
}
private static void PreProcess2(string fgImagePath, string savePath, string scriptPath) if (dictionary != null)
{
HashSet<FgRecord> fgRecords = FetchFgObjectsFromScript(scriptPath);
if (fgRecords.Count == 0)
throw new Exception("No valid fg object found.");
//重新写个我也懒得将FgRecord转FgObject了
//Tips:如果有单独饰品素材,可能前面的解析会有遗漏 //反正遥かなるニライカナイ里没有戴眼镜的角色 (笑
Process2(fgImagePath, savePath, fgRecords);
}
private static void PreProcess(string fgImagePath, string savePath, string luaFilePath)
{
Dictionary<object, object> dictionary = ParseLuaTable(luaFilePath) ?? throw new Exception("Lua table parsing failed");
if (dictionary["fg"] is Dictionary<object, object> fgDictionary)
{ {
if (fgDictionary["size"] is not List<object> size || size.Count == 0) if (dictionary["fg"] is Dictionary<object, object> fgDictionary)
throw new Exception("size not found or empty");
fgDictionary.Remove("size");
List<FgObject> fgObjects = [];
foreach (var fg in fgDictionary)
{ {
if (fg.Value is Dictionary<object, object> fgValue) if (fgDictionary["size"] is not List<object> size || size.Count == 0)
{ {
var fuku = ConvertToStringList(fgValue["fuku"] as List<object>); throw new Exception("size not found or empty");
var pose = ConvertToNestedStringList(fgValue["pose"] as List<object>);
var face = ConvertToStringDictionary(fgValue["face"] as Dictionary<object, object>);
//check null
if (fgValue["path"] is not string path || fgValue["head"] is not string head || fuku == null || pose == null || face == null)
throw new Exception("fg object has null value");
path = path[4..]; // remove :fg/../
fgObjects.Add(new FgObject(path, head, fuku, pose, face));
} }
} fgDictionary.Remove("size");
Process(fgImagePath, savePath, size, fgObjects);
}
else
Console.WriteLine("fg not found");
}
private static void Process2(string fgImagePath, string savePath, HashSet<FgRecord> fgRecords) //convert to FgObject
{ List<FgObject> fgObjects = [];
var parallelOptions = new ParallelOptions foreach (var fg in fgDictionary)
{
MaxDegreeOfParallelism = 6 // 设置最大并行度
};
Parallel.ForEach(fgRecords, parallelOptions, fgRecord =>
{
string originImageBase = Path.Combine(fgImagePath, fgRecord.ChName, fgRecord.Size, fgRecord.File + ".png");
string originImageFace = Path.Combine(fgImagePath, fgRecord.ChName, fgRecord.Size, fgRecord.Face + ".png");
string targetFilename = $"{fgRecord.File}_{fgRecord.Face}.png";
string targetPath = Path.Combine(savePath, fgRecord.Size);
if (!File.Exists(originImageBase) || !File.Exists(originImageFace))
{
Console.WriteLine("ERROR, Image not found. Details:");
Console.WriteLine($"Base: {originImageBase}");
Console.WriteLine($"Face: {originImageFace}");
return;
}
if (!Directory.Exists(targetPath))
Directory.CreateDirectory(targetPath);
targetPath = Path.Combine(targetPath, targetFilename);
if (File.Exists(targetPath))
{
Console.WriteLine("File already exists, skipping...");
return;
}
ProcessAndSaveLite(originImageBase, originImageFace, targetPath);
});
}
private static void Process(string fgImagePath, string savePath, List<object> size, List<FgObject> fgObjects)
{
foreach (var fgObject in fgObjects)
{
foreach (var siz in size)
{
if (siz != null && fgObject.Path != null)
{ {
string savePathWithSizePart = Path.Combine(savePath, fgObject.Path, siz.ToString() ?? string.Empty); if (fg.Value is Dictionary<object, object> fgValue)
string pathWithSize = Path.Combine(fgImagePath, fgObject.Path, siz.ToString() ?? string.Empty);
foreach (var pose in fgObject.Pose)
{ {
Parallel.ForEach(fgObject.Fuku, fuku => var fuku = ConvertToStringList(fgValue["fuku"] as List<object>);
var pose = ConvertToNestedStringList(fgValue["pose"] as List<object>);
var face = ConvertToStringDictionary(fgValue["face"] as Dictionary<object, object>);
//check null
if (fgValue["path"] is not string path || fgValue["head"] is not string head || fuku == null || pose == null || face == null)
{ {
//💢 skip //For ハミダシクリエイティブ Console.WriteLine("fg object has null value");
if (fuku == "99") continue;
}
path = path[4..]; // remove :fg/../
fgObjects.Add(new FgObject(path, head, fuku, pose, face));
}
}
foreach (var fgObject in fgObjects)
{
foreach (var siz in size)
{
if (siz != null && fgObject.Path != null)
{
string savePathWithSizePart = Path.Combine(savePath, fgObject.Path, siz.ToString() ?? string.Empty);
string pathWithSize = Path.Combine(fgImagePath, fgObject.Path, siz.ToString() ?? string.Empty);
foreach (var pose in fgObject.Pose)
{ {
return; Parallel.ForEach(fgObject.Fuku, fuku =>
} {
bool special = false; //💢 skip //For ハミダシクリエイティブ
string special_text = ""; if (fuku == "99")
string fuku_current = fuku; {
int index = fuku_current.IndexOf('|'); return;
if (index != -1) }
{ bool special = false;
special = true; string special_text="";
special_text = fuku_current[(index + 1)..]; string fuku_current = fuku;
fuku_current = fuku_current[..index]; int index = fuku_current.IndexOf('|');
} if (index != -1)
// <head><siz><pose[0]><fuku><pose[1]>0 {
// *sp:fuku: 02 | 0099→02fuku & 0099face special = true;
string baseImg = Path.Combine(pathWithSize, $"{fgObject.Head}{siz}{pose[0]}{fuku_current}{pose[1]}0.png"); special_text = fuku_current[(index + 1)..];
foreach (var face in fgObject.Face[pose[0]]) fuku_current = fuku_current[..index];
{ }
string layerImg = Path.Combine(pathWithSize, $"{face}.png"); // <head><siz><pose[0]><fuku><pose[1]>0
string layer2Img = special ? Path.Combine(pathWithSize, $"{pose[0]}{special_text}.png") : ""; //眼镜 // *sp:fuku: 02 | 0099→02fuku & 0099face
string savePathWithAll = Path.Combine(savePathWithSizePart, $"{fgObject.Head}{siz}{pose[0]}{fuku_current}{pose[1]}0_{face}" + (special ? ($"_{pose[0]}{special_text}.png") : (".png"))); string baseImg = Path.Combine(pathWithSize, $"{fgObject.Head}{siz}{pose[0]}{fuku_current}{pose[1]}0.png");
ProcessAndSave(baseImg, layerImg, layer2Img, savePathWithAll, special); foreach (var face in fgObject.Face[pose[0]])
} {
string layerImg = Path.Combine(pathWithSize, $"{face}.png");
string layer2Img = special ? Path.Combine(pathWithSize, $"{pose[0]}{special_text}.png") : ""; //眼镜
string savePathWithAll = Path.Combine(savePathWithSizePart, $"{fgObject.Head}{siz}{pose[0]}{fuku_current}{pose[1]}0_{face}" + (special ? ($"_{pose[0]}{special_text}.png") : (".png")));
ProcessAndSave(baseImg, layerImg, layer2Img, savePathWithAll, special);
}
}); });
}
}
}
string siz2 = "fa"; //别急着换下一个还有个fa //这里的代码和上面那块一样
if (fgObject.Path != null)
{
string savePathWithSizePart = Path.Combine(savePath, fgObject.Path, siz2.ToString() ?? string.Empty);
string pathWithSize = Path.Combine(fgImagePath, fgObject.Path, siz2.ToString() ?? string.Empty);
foreach (var pose in fgObject.Pose)
{
Parallel.ForEach(fgObject.Fuku, fuku =>
{
//💢 skip //For ハミダシクリエイティブ
if (fuku == "99")
{
return;
}
bool special = false;
string special_text = "";
string fuku_current = fuku;
int index = fuku_current.IndexOf('|');
if (index != -1)
{
special = true;
special_text = fuku_current[(index + 1)..];
fuku_current = fuku_current[..index];
}
string baseImg = Path.Combine(pathWithSize, $"{fgObject.Head}no{pose[0]}{fuku_current}{pose[1]}0.png");
foreach (var face in fgObject.Face[pose[0]])
{
string layerImg = Path.Combine(pathWithSize, $"{face}.png");
string layer2Img = special ? Path.Combine(pathWithSize, $"{pose[0]}{special_text}.png") : "";
string savePathWithAll = Path.Combine(savePathWithSizePart, $"{fgObject.Head}no{pose[0]}{fuku_current}{pose[1]}0_{face}" + (special ? ($"_{pose[0]}{special_text}.png") : (".png")));
ProcessAndSave(baseImg, layerImg, layer2Img, savePathWithAll, special);
}
});
}
} }
} }
} }
string siz2 = "fa"; //别急着换下一个还有个fa //这里的代码和上面那块一样 else
if (fgObject.Path != null)
{ {
string savePathWithSizePart = Path.Combine(savePath, fgObject.Path, siz2.ToString() ?? string.Empty); Console.WriteLine("fg not found");
string pathWithSize = Path.Combine(fgImagePath, fgObject.Path, siz2.ToString() ?? string.Empty);
foreach (var pose in fgObject.Pose)
{
Parallel.ForEach(fgObject.Fuku, fuku =>
{
//💢 skip //For ハミダシクリエイティブ
if (fuku == "99")
{
return;
}
bool special = false;
string special_text = "";
string fuku_current = fuku;
int index = fuku_current.IndexOf('|');
if (index != -1)
{
special = true;
special_text = fuku_current[(index + 1)..];
fuku_current = fuku_current[..index];
}
string baseImg = Path.Combine(pathWithSize, $"{fgObject.Head}no{pose[0]}{fuku_current}{pose[1]}0.png");
foreach (var face in fgObject.Face[pose[0]])
{
string layerImg = Path.Combine(pathWithSize, $"{face}.png");
string layer2Img = special ? Path.Combine(pathWithSize, $"{pose[0]}{special_text}.png") : "";
string savePathWithAll = Path.Combine(savePathWithSizePart, $"{fgObject.Head}no{pose[0]}{fuku_current}{pose[1]}0_{face}" + (special ? ($"_{pose[0]}{special_text}.png") : (".png")));
ProcessAndSave(baseImg, layerImg, layer2Img, savePathWithAll, special);
}
});
}
} }
} }
} }
private static void ProcessAndSaveLite(string baseImg, string faceImg, string target)
{
using MagickImage firstImage = new(baseImg);
List<int> comment1 = ReadPngComment(firstImage.Comment); //base
using MagickImage secondImage = new(faceImg);
List<int> comment2 = ReadPngComment(secondImage.Comment); //face
int x = comment2[0] - comment1[0]; // face x - base x
int y = comment2[1] - comment1[1]; // face y - base y
firstImage.Composite(secondImage, x, y, CompositeOperator.Over);
firstImage.Write(target);
Console.WriteLine($"Image {Path.GetFileName(target)} processing completed.");
}
private static void ProcessAndSave(string baseImg, string layerImg, string layer2Img, string target, bool special) private static void ProcessAndSave(string baseImg, string layerImg, string layer2Img, string target, bool special)
{ {
if (File.Exists(target)) if (File.Exists(target))
@ -264,7 +206,9 @@ namespace ArtemisFgTools
LuaTable luaTable = lua.GetTable("exfgtable"); LuaTable luaTable = lua.GetTable("exfgtable");
if (luaTable != null) if (luaTable != null)
{
return (Dictionary<object, object>?)LuaTableToSs(luaTable); return (Dictionary<object, object>?)LuaTableToSs(luaTable);
}
else else
{ {
Console.WriteLine("Lua table not found"); Console.WriteLine("Lua table not found");

View File

@ -1,8 +0,0 @@
{
"profiles": {
"ArtemisFgTools": {
"commandName": "Project",
"commandLineArgs": "-c\r\nG:\\x221.local\\1\\lab\\fg\r\n-s\r\nG:\\x221.local\\1\\lab\\script\r\n-o\r\nG:\\x221.local\\1\\lab\\output"
}
}
}