Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
52cd21a064 | |||
6e5f3e5fdc | |||
9160ba7505 | |||
4d1403958f | |||
cec82141fc | |||
00e8bcc038 | |||
bbe55d4c56 | |||
119ecb9cf9 | |||
2559d4a438 | |||
3848c7c446 |
@ -8,8 +8,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="13.10.0" />
|
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.0.0" />
|
||||||
<PackageReference Include="Magick.NET.Core" Version="13.10.0" />
|
<PackageReference Include="Magick.NET.Core" Version="14.0.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>
|
||||||
|
200
ArtemisFgTools/FgHelper.cs
Normal file
200
ArtemisFgTools/FgHelper.cs
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
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> FetchFgRecordsFromScript(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 HashSet<FgRecord> FetchFgObjectsDirect(string path)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
throw new DirectoryNotFoundException("The path does not exist.");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HashSet<FgRecord> fgRecords = [];
|
||||||
|
//获取path文件夹下所有文件夹(不含子文件夹)
|
||||||
|
string[] charaDirs = Directory.GetDirectories(path);
|
||||||
|
foreach (string charaDir in charaDirs)
|
||||||
|
{
|
||||||
|
string chName = Path.GetFileName(charaDir);
|
||||||
|
string[] sizeDirs = Directory.GetDirectories(charaDir);
|
||||||
|
foreach (string sizeDir in sizeDirs)
|
||||||
|
{
|
||||||
|
string size = Path.GetFileName(sizeDir);
|
||||||
|
string[] files = Directory.GetFiles(sizeDir, "*.png");
|
||||||
|
Dictionary<string, List<string>> baseFiles = [];
|
||||||
|
Dictionary<string, List<string>> faceFiles = [];
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
string fileName = Path.GetFileNameWithoutExtension(file);
|
||||||
|
if (fileName.StartsWith(chName)) //base
|
||||||
|
{
|
||||||
|
string[] parts = fileName.Split('_');
|
||||||
|
if (parts.Length < 2)
|
||||||
|
throw new Exception("Not supported character name format.");
|
||||||
|
string category = parts[1][2].ToString();
|
||||||
|
if (!baseFiles.TryGetValue(category, out List<string>? value))
|
||||||
|
{
|
||||||
|
value = ([]);
|
||||||
|
baseFiles[category] = value;
|
||||||
|
}
|
||||||
|
value.Add(fileName);
|
||||||
|
}
|
||||||
|
else //face
|
||||||
|
{
|
||||||
|
string category = fileName[0].ToString();
|
||||||
|
if (!faceFiles.TryGetValue(category, out List<string>? value))
|
||||||
|
{
|
||||||
|
value = ([]);
|
||||||
|
faceFiles[category] = value;
|
||||||
|
}
|
||||||
|
value.Add(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
foreach (var baseEntry in baseFiles)
|
||||||
|
{
|
||||||
|
string baseCategory = baseEntry.Key; // 获取 base 文件的类别
|
||||||
|
var baseFileNames = baseEntry.Value; // 获取对应的 base 文件名列表
|
||||||
|
// 检查是否存在相同类别的 face 文件
|
||||||
|
if (faceFiles.TryGetValue(baseCategory, out List<string>? faceFileNames))
|
||||||
|
{
|
||||||
|
foreach (var baseFile in baseFileNames)
|
||||||
|
{
|
||||||
|
foreach (var faceFile in faceFileNames)
|
||||||
|
{
|
||||||
|
FgRecord fgRecord = new()
|
||||||
|
{
|
||||||
|
ChName = chName,
|
||||||
|
Size = size,
|
||||||
|
File = baseFile,
|
||||||
|
Face = faceFile
|
||||||
|
};
|
||||||
|
fgRecords.Add(fgRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,160 +1,243 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
public class FgObject(string path, string head, List<string> fuku, List<List<string>> pose, Dictionary<string, List<string>> face)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
public string Path { get; set; } = path;
|
if (args.Length == 1)
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
Console.WriteLine("路径不能为空");
|
if (args[0] == "-h")
|
||||||
return;
|
Console.WriteLine("Usage: tools.exe -c <fgPath> (-s <scriptPath> | -t <luaTablePath>) -o <outputPath>");
|
||||||
|
else
|
||||||
|
Console.WriteLine("Invalid arguments, Please check the usage via -h");
|
||||||
}
|
}
|
||||||
if (!Directory.Exists(fgImagePath) || !File.Exists(luaFilePath))
|
else if(args.Length == 4)
|
||||||
{
|
{
|
||||||
Console.WriteLine("路径不存在");
|
//暴力合成
|
||||||
return;
|
if (args[0] != "-c" || args[2] != "-o")
|
||||||
}
|
Console.WriteLine("Invalid arguments, Please check the usage via -h");
|
||||||
if (!Directory.Exists(savePath))
|
else
|
||||||
{
|
|
||||||
Directory.CreateDirectory(savePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary<object, object>? dictionary = ParseLuaTable(luaFilePath);
|
|
||||||
|
|
||||||
if (dictionary != null)
|
|
||||||
{
|
|
||||||
if (dictionary["fg"] is Dictionary<object, object> fgDictionary)
|
|
||||||
{
|
{
|
||||||
if (fgDictionary["size"] is not List<object> size || size.Count == 0)
|
if (!Directory.Exists(args[3]))
|
||||||
{
|
Directory.CreateDirectory(args[3]);
|
||||||
throw new Exception("size not found or empty");
|
if (!Directory.Exists(args[1]))
|
||||||
}
|
Console.WriteLine("Invalid fg path");
|
||||||
fgDictionary.Remove("size");
|
else
|
||||||
|
PreProcess3(args[1], args[3]);
|
||||||
|
}
|
||||||
|
|
||||||
//convert to FgObject
|
}
|
||||||
List<FgObject> fgObjects = [];
|
else if (args.Length != 6)
|
||||||
foreach (var fg in fgDictionary)
|
Console.WriteLine("Invalid arguments, Please check the usage via -h");
|
||||||
{
|
else if (args[0] != "-c" || !(args[2] == "-s" || args[2] == "-t") || args[4] != "-o")
|
||||||
if (fg.Value is Dictionary<object, object> fgValue)
|
Console.WriteLine("Invalid arguments, Please check the usage via -h");
|
||||||
{
|
else
|
||||||
var fuku = ConvertToStringList(fgValue["fuku"] as List<object>);
|
{
|
||||||
var pose = ConvertToNestedStringList(fgValue["pose"] as List<object>);
|
if (!Directory.Exists(args[5]))
|
||||||
var face = ConvertToStringDictionary(fgValue["face"] as Dictionary<object, object>);
|
Directory.CreateDirectory(args[5]);
|
||||||
//check null
|
if (!Directory.Exists(args[1]))
|
||||||
if (fgValue["path"] is not string path || fgValue["head"] is not string head || fuku == null || pose == null || face == null)
|
Console.WriteLine("Invalid fg path");
|
||||||
{
|
else if (args[2] == "-s")
|
||||||
Console.WriteLine("fg object has null value");
|
{
|
||||||
continue;
|
if (!Directory.Exists(args[3]))
|
||||||
}
|
Console.WriteLine("Invalid script path");
|
||||||
path = path[4..]; // remove :fg/../
|
else
|
||||||
fgObjects.Add(new FgObject(path, head, fuku, pose, face));
|
PreProcess2(args[1], args[5], args[3]);
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
// <head><siz><pose[0]><fuku><pose[1]>0
|
|
||||||
// *sp:fuku: 02 | 0099→02fuku & 0099face
|
|
||||||
string baseImg = Path.Combine(pathWithSize, $"{fgObject.Head}{siz}{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}{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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine("fg not found");
|
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...");
|
||||||
|
Console.ReadKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PreProcess3(string fgImagePath, string savePath)
|
||||||
|
{
|
||||||
|
//直接暴力处理,只认定底和脸
|
||||||
|
HashSet<FgRecord> fgRecords = FetchFgObjectsDirect(fgImagePath);
|
||||||
|
if (fgRecords.Count == 0)
|
||||||
|
throw new Exception("No valid fg object found.");
|
||||||
|
Process2(fgImagePath, savePath, fgRecords);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PreProcess2(string fgImagePath, string savePath, string scriptPath)
|
||||||
|
{
|
||||||
|
HashSet<FgRecord> fgRecords = FetchFgRecordsFromScript(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)
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
throw new Exception("fg object has null value");
|
||||||
|
path = path[4..]; // remove :fg/../
|
||||||
|
fgObjects.Add(new FgObject(path, head, fuku, pose, face));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Process(fgImagePath, savePath, size, fgObjects);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Console.WriteLine("fg not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Process2(string fgImagePath, string savePath, HashSet<FgRecord> fgRecords)
|
||||||
|
{
|
||||||
|
var parallelOptions = new ParallelOptions
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
string pathWithSize = Path.Combine(fgImagePath, fgObject.Path, siz.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];
|
||||||
|
}
|
||||||
|
// <head><siz><pose[0]><fuku><pose[1]>0
|
||||||
|
// *sp:fuku: 02 | 0099→02fuku & 0099face
|
||||||
|
string baseImg = Path.Combine(pathWithSize, $"{fgObject.Head}{siz}{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}{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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
@ -206,9 +289,7 @@ 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");
|
||||||
|
8
ArtemisFgTools/Properties/launchSettings.json
Normal file
8
ArtemisFgTools/Properties/launchSettings.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"ArtemisFgTools": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "-c\r\nG:\\x221.local\\1\\lab\\fg\r\n-o\r\nG:\\x221.local\\1\\lab\\output"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,3 +17,11 @@
|
|||||||
等待完成...
|
等待完成...
|
||||||
|
|
||||||
![](https://git.chenx221.cyou/chenx221/ArtemisFgTools/raw/branch/master/img/2024-07-28_172940.jpg)
|
![](https://git.chenx221.cyou/chenx221/ArtemisFgTools/raw/branch/master/img/2024-07-28_172940.jpg)
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
セレクトオブリージュ全部合并,大小约14.4 GB
|
||||||
|
|
||||||
|
ハミダシクリエイティブ全部合并,大小约27.0 GB
|
||||||
|
|
||||||
|
(应该可以通过修改exlist内容实现合并部分内容)
|
46
readme.md
Normal file
46
readme.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# ArtemisFgTools
|
||||||
|
|
||||||
|
## 立绘合成
|
||||||
|
|
||||||
|
```
|
||||||
|
//你可以通过ArtemisFgTools.exe -h了解基础用法
|
||||||
|
Usage: tools.exe -c <fgPath> (-s <scriptPath> | -t <luaTablePath>) -o <outputPath>
|
||||||
|
|
||||||
|
<fgPath>: fg素材文件夹, 文件夹应该包含了一堆角色名的子文件夹
|
||||||
|
<scriptPath>: script脚本文件夹, 文件夹应该包含了一堆.ast游戏脚本
|
||||||
|
<luaTablePath>: 游戏数据表的路径, 部分游戏有提供
|
||||||
|
<outputPath>: 最后保存的位置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 经过测试的游戏
|
||||||
|
|
||||||
|
- セレクトオブリージュ (表: pc\ja\extra\exlist.ipt)
|
||||||
|
|
||||||
|
- ハミダシクリエイティブ (表: system\table\exlist.tbl)
|
||||||
|
|
||||||
|
- 遥かなるニライカナイ (没找到表)
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
1. 提取fg文件夹 (image\fg) 和表文件 (如果有)
|
||||||
|
|
||||||
|
2. 根据游戏选择合适方法
|
||||||
|
|
||||||
|
- 有数据表:
|
||||||
|
|
||||||
|
```tools.exe -c <fgPath> -t <luaTablePath> -o <outputPath>```
|
||||||
|
|
||||||
|
- 没数据表:
|
||||||
|
|
||||||
|
1. 读取所有脚本, 根据游戏中的情况合并图像(可能有大量遗漏,所有图像素材会被分类为base和face)
|
||||||
|
|
||||||
|
```tools.exe -c <fgPath> -s <scriptPath> -o <outputPath>```
|
||||||
|
|
||||||
|
2. 直接根据图像素材名分类并组合(没有遗漏,所有图像素材会被分类为base和face)
|
||||||
|
|
||||||
|
```tools.exe -c <fgPath> -o <outputPath>```
|
||||||
|
|
||||||
|
### 旧版教程
|
||||||
|
|
||||||
|
[链接](README1.md)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user