376 lines
14 KiB
C#
376 lines
14 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using AIImages.Models;
|
|
using RimWorld;
|
|
using Verse;
|
|
|
|
namespace AIImages.Services
|
|
{
|
|
/// <summary>
|
|
/// Продвинутый генератор промптов для Stable Diffusion
|
|
/// </summary>
|
|
public class AdvancedPromptGenerator : IPromptGeneratorService
|
|
{
|
|
private static readonly Dictionary<string, string> TraitToMood = new Dictionary<
|
|
string,
|
|
string
|
|
>
|
|
{
|
|
{ "Kind", "warm smile, gentle expression, friendly eyes" },
|
|
{ "Bloodlust", "intense gaze, fierce expression, aggressive posture" },
|
|
{ "Psychopath", "cold eyes, emotionless face, calculating expression" },
|
|
{ "Pessimist", "sad eyes, worried expression, downcast gaze" },
|
|
{ "Optimist", "bright smile, hopeful expression, cheerful demeanor" },
|
|
{ "Nervous", "anxious expression, tense posture, worried eyes" },
|
|
{ "Careful", "focused expression, attentive gaze, composed posture" },
|
|
{ "Brave", "confident expression, determined gaze, strong posture" },
|
|
{ "Wimp", "fearful eyes, nervous expression, timid posture" },
|
|
{ "Greedy", "cunning expression, calculating eyes, sly smile" },
|
|
{ "Jealous", "envious gaze, bitter expression, tense face" },
|
|
{ "Ascetic", "serene expression, calm demeanor, peaceful eyes" },
|
|
{ "Beautiful", "stunning features, attractive appearance, graceful" },
|
|
{ "Ugly", "rough features, weathered appearance" },
|
|
{ "Pretty", "attractive features, pleasant appearance, charming" },
|
|
};
|
|
|
|
private static readonly Dictionary<ArtStyle, string> ArtStylePrompts = new Dictionary<
|
|
ArtStyle,
|
|
string
|
|
>
|
|
{
|
|
{ ArtStyle.None, "" },
|
|
{ ArtStyle.Realistic, "photorealistic, hyperrealistic, realistic photo, photography" },
|
|
{ ArtStyle.SemiRealistic, "semi-realistic, detailed illustration, realistic art" },
|
|
{ ArtStyle.Anime, "anime style, manga style, anime character" },
|
|
{
|
|
ArtStyle.ConceptArt,
|
|
"concept art, digital art, artstation, professional concept design"
|
|
},
|
|
{ ArtStyle.DigitalPainting, "digital painting, painterly, brush strokes, artistic" },
|
|
{ ArtStyle.OilPainting, "oil painting, traditional painting, canvas, fine art" },
|
|
{ ArtStyle.Sketch, "pencil sketch, hand drawn, sketch art, line art" },
|
|
{ ArtStyle.CellShaded, "cell shaded, flat colors, toon shading, stylized" },
|
|
};
|
|
|
|
public string GeneratePositivePrompt(
|
|
PawnAppearanceData appearanceData,
|
|
StableDiffusionSettings settings
|
|
)
|
|
{
|
|
if (appearanceData == null)
|
|
return "portrait of a person";
|
|
|
|
StringBuilder prompt = new StringBuilder();
|
|
|
|
// 1. Художественный стиль
|
|
if (
|
|
ArtStylePrompts.TryGetValue(settings.ArtStyle, out string stylePrompt)
|
|
&& !string.IsNullOrEmpty(stylePrompt)
|
|
)
|
|
{
|
|
prompt.Append(stylePrompt);
|
|
prompt.Append(", ");
|
|
}
|
|
|
|
// 2. Тип кадра - автоматически добавляем "portrait" для генерации персонажей
|
|
prompt.Append("portrait, head and shoulders of ");
|
|
|
|
// 3. Базовое описание (возраст и пол)
|
|
prompt.Append(GetAgeAndGenderDescription(appearanceData));
|
|
prompt.Append(", ");
|
|
|
|
// 4. Тип тела
|
|
string bodyType = GetBodyTypeDescription(appearanceData.BodyType);
|
|
if (!string.IsNullOrEmpty(bodyType))
|
|
{
|
|
prompt.Append(bodyType);
|
|
prompt.Append(", ");
|
|
}
|
|
|
|
// 5. Цвет кожи
|
|
string skinTone = ColorDescriptionService.GetSkinToneDescription(
|
|
appearanceData.SkinColor
|
|
);
|
|
prompt.Append(skinTone);
|
|
prompt.Append(", ");
|
|
|
|
// 6. Волосы
|
|
string hairDescription = GetHairDescription(appearanceData);
|
|
if (!string.IsNullOrEmpty(hairDescription))
|
|
{
|
|
prompt.Append(hairDescription);
|
|
prompt.Append(", ");
|
|
}
|
|
|
|
// 7. Настроение и выражение на основе черт характера
|
|
string moodDescription = GetMoodFromTraits(appearanceData.Traits);
|
|
if (!string.IsNullOrEmpty(moodDescription))
|
|
{
|
|
prompt.Append(moodDescription);
|
|
prompt.Append(", ");
|
|
}
|
|
|
|
// 8. Одежда
|
|
string apparelDescription = GetApparelDescription(appearanceData.Apparel);
|
|
if (!string.IsNullOrEmpty(apparelDescription))
|
|
{
|
|
prompt.Append(apparelDescription);
|
|
prompt.Append(", ");
|
|
}
|
|
|
|
// 9. Базовый пользовательский промпт (если указан)
|
|
if (!string.IsNullOrEmpty(settings.PositivePrompt))
|
|
{
|
|
prompt.Append(settings.PositivePrompt);
|
|
prompt.Append(", ");
|
|
}
|
|
|
|
// 10. Качественные теги
|
|
prompt.Append(GetQualityTags(settings.ArtStyle));
|
|
|
|
return prompt.ToString().Trim().TrimEnd(',');
|
|
}
|
|
|
|
public string GenerateNegativePrompt(StableDiffusionSettings settings)
|
|
{
|
|
StringBuilder negativePrompt = new StringBuilder();
|
|
|
|
// Базовые негативные промпты
|
|
negativePrompt.Append(
|
|
"ugly, deformed, low quality, blurry, bad anatomy, worst quality, "
|
|
);
|
|
negativePrompt.Append(
|
|
"mutated, disfigured, bad proportions, extra limbs, missing limbs, "
|
|
);
|
|
|
|
// Специфичные для стиля негативы
|
|
switch (settings.ArtStyle)
|
|
{
|
|
case ArtStyle.Realistic:
|
|
case ArtStyle.SemiRealistic:
|
|
negativePrompt.Append("cartoon, anime, painting, drawing, illustration, ");
|
|
break;
|
|
case ArtStyle.Anime:
|
|
negativePrompt.Append("realistic, photo, photography, 3d, ");
|
|
break;
|
|
case ArtStyle.None:
|
|
// Без дополнительных негативных промптов для стиля None
|
|
break;
|
|
}
|
|
|
|
// Пользовательский негативный промпт
|
|
if (!string.IsNullOrEmpty(settings.NegativePrompt))
|
|
{
|
|
negativePrompt.Append(settings.NegativePrompt);
|
|
}
|
|
|
|
return negativePrompt.ToString().Trim().TrimEnd(',');
|
|
}
|
|
|
|
public string GetFullPromptDescription(
|
|
PawnAppearanceData appearanceData,
|
|
StableDiffusionSettings settings
|
|
)
|
|
{
|
|
StringBuilder description = new StringBuilder();
|
|
|
|
description.AppendLine("=== Positive Prompt ===");
|
|
description.AppendLine(GeneratePositivePrompt(appearanceData, settings));
|
|
description.AppendLine();
|
|
|
|
description.AppendLine("=== Negative Prompt ===");
|
|
description.AppendLine(GenerateNegativePrompt(settings));
|
|
description.AppendLine();
|
|
|
|
description.AppendLine("=== Technical Parameters ===");
|
|
description.AppendLine($"Steps: {settings.Steps}");
|
|
description.AppendLine($"CFG Scale: {settings.CfgScale}");
|
|
description.AppendLine($"Size: {settings.Width}x{settings.Height}");
|
|
description.AppendLine($"Sampler: {settings.Sampler}");
|
|
description.AppendLine(
|
|
$"Seed: {(settings.Seed == -1 ? "Random" : settings.Seed.ToString())}"
|
|
);
|
|
|
|
return description.ToString();
|
|
}
|
|
|
|
private string GetAgeAndGenderDescription(PawnAppearanceData data)
|
|
{
|
|
string ageGroup = data.Age switch
|
|
{
|
|
< 18 => "young",
|
|
< 25 => "young adult",
|
|
< 35 => "adult",
|
|
< 50 => "middle-aged",
|
|
< 65 => "mature",
|
|
_ => "elderly",
|
|
};
|
|
|
|
string genderLabel = data.Gender switch
|
|
{
|
|
Gender.Male => "man",
|
|
Gender.Female => "woman",
|
|
_ => "person",
|
|
};
|
|
return $"{ageGroup} {genderLabel}";
|
|
}
|
|
|
|
private string GetBodyTypeDescription(string bodyType)
|
|
{
|
|
if (string.IsNullOrEmpty(bodyType))
|
|
return "";
|
|
|
|
return bodyType.ToLower() switch
|
|
{
|
|
"thin" => "slender build, lean physique",
|
|
"hulk" => "muscular build, strong physique, athletic body",
|
|
"fat" => "heavyset build, stocky physique",
|
|
"female" => "feminine build",
|
|
"male" => "masculine build",
|
|
_ => "average build",
|
|
};
|
|
}
|
|
|
|
private string GetHairDescription(PawnAppearanceData data)
|
|
{
|
|
if (string.IsNullOrEmpty(data.HairDefName))
|
|
return "";
|
|
|
|
StringBuilder hair = new StringBuilder();
|
|
|
|
// Цвет волос
|
|
string hairColor = ColorDescriptionService.GetHairColorDescription(data.HairColor);
|
|
hair.Append(hairColor);
|
|
hair.Append(" ");
|
|
|
|
// Стиль прически - используем DefName для английского названия
|
|
string style = CleanDefName(data.HairDefName)
|
|
.Replace("shaved", "very short")
|
|
.Replace("mohawk", "mohawk hairstyle");
|
|
|
|
hair.Append(style);
|
|
hair.Append(" hair");
|
|
|
|
return hair.ToString();
|
|
}
|
|
|
|
private string GetMoodFromTraits(List<Trait> traits)
|
|
{
|
|
if (traits == null || !traits.Any())
|
|
return "neutral expression";
|
|
|
|
// Ищем черты, которые влияют на внешний вид
|
|
foreach (var trait in traits)
|
|
{
|
|
string traitDefName = trait.def.defName;
|
|
if (TraitToMood.TryGetValue(traitDefName, out string mood))
|
|
{
|
|
return mood;
|
|
}
|
|
}
|
|
|
|
return "calm expression";
|
|
}
|
|
|
|
private string GetApparelDescription(List<ApparelData> apparel)
|
|
{
|
|
if (apparel == null || !apparel.Any())
|
|
return "simple clothes";
|
|
|
|
StringBuilder apparelDesc = new StringBuilder("wearing ");
|
|
|
|
// Берем топ 5 наиболее заметных предметов одежды
|
|
var visibleApparel = apparel.Take(5).ToList();
|
|
|
|
List<string> items = new List<string>();
|
|
foreach (var item in visibleApparel)
|
|
{
|
|
StringBuilder itemDesc = new StringBuilder();
|
|
|
|
// Цвет (если не белый)
|
|
if (item.Color != UnityEngine.Color.white)
|
|
{
|
|
string colorDesc = ColorDescriptionService.GetApparelColorDescription(
|
|
item.Color
|
|
);
|
|
itemDesc.Append(colorDesc);
|
|
itemDesc.Append(" ");
|
|
}
|
|
|
|
// Материал - используем DefName для английского названия
|
|
if (!string.IsNullOrEmpty(item.MaterialDefName))
|
|
{
|
|
itemDesc.Append(CleanDefName(item.MaterialDefName));
|
|
itemDesc.Append(" ");
|
|
}
|
|
|
|
// Название предмета - используем DefName для английского названия
|
|
itemDesc.Append(CleanDefName(item.DefName));
|
|
|
|
items.Add(itemDesc.ToString());
|
|
}
|
|
|
|
apparelDesc.Append(string.Join(", ", items));
|
|
|
|
return apparelDesc.ToString();
|
|
}
|
|
|
|
private string GetQualityTags(ArtStyle style)
|
|
{
|
|
var baseTags = "highly detailed, professional, masterpiece, best quality";
|
|
|
|
switch (style)
|
|
{
|
|
case ArtStyle.None:
|
|
return baseTags;
|
|
case ArtStyle.Realistic:
|
|
case ArtStyle.SemiRealistic:
|
|
return $"{baseTags}, professional photography, 8k uhd, dslr, high quality, sharp focus";
|
|
case ArtStyle.Anime:
|
|
return $"{baseTags}, anime masterpiece, high resolution, vibrant colors";
|
|
case ArtStyle.ConceptArt:
|
|
return $"{baseTags}, trending on artstation, professional digital art";
|
|
default:
|
|
return baseTags;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Преобразует defName в читаемый английский текст для промпта
|
|
/// Пример: "Apparel_Pants" -> "pants", "Synthread" -> "synthread"
|
|
/// </summary>
|
|
private string CleanDefName(string defName)
|
|
{
|
|
if (string.IsNullOrEmpty(defName))
|
|
return "";
|
|
|
|
string cleaned = defName;
|
|
|
|
// Убираем распространенные префиксы RimWorld
|
|
cleaned = System.Text.RegularExpressions.Regex.Replace(
|
|
cleaned,
|
|
"^(Apparel_|Armor_|Weapon_|Thing_)",
|
|
"",
|
|
System.Text.RegularExpressions.RegexOptions.IgnoreCase
|
|
);
|
|
|
|
// Разделяем CamelCase на слова
|
|
cleaned = System.Text.RegularExpressions.Regex.Replace(
|
|
cleaned,
|
|
"([a-z])([A-Z])",
|
|
"$1 $2"
|
|
);
|
|
|
|
// Заменяем подчеркивания на пробелы
|
|
cleaned = cleaned.Replace("_", " ");
|
|
|
|
// Убираем множественные пробелы
|
|
cleaned = System.Text.RegularExpressions.Regex.Replace(cleaned, @"\s+", " ");
|
|
|
|
// Приводим к нижнему регистру и убираем лишние пробелы
|
|
return cleaned.Trim().ToLower();
|
|
}
|
|
}
|
|
}
|