using System.Collections.Generic; using System.Linq; using System.Text; using AIImages.Models; using RimWorld; using Verse; namespace AIImages.Services { /// /// Продвинутый генератор промптов для Stable Diffusion /// public class AdvancedPromptGenerator : IPromptGeneratorService { private static readonly Dictionary 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 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 (!string.IsNullOrEmpty(settings.PositivePrompt)) { prompt.Append(settings.PositivePrompt); prompt.Append(", "); } // 2. Художественный стиль if ( ArtStylePrompts.TryGetValue(settings.ArtStyle, out string stylePrompt) && !string.IsNullOrEmpty(stylePrompt) ) { prompt.Append(stylePrompt); prompt.Append(", "); } // 3. Тип кадра - автоматически добавляем "portrait" для генерации персонажей prompt.Append("portrait, head and shoulders of "); // 4. Базовое описание (возраст и пол) prompt.Append(GetAgeAndGenderDescription(appearanceData)); prompt.Append(", "); // 5. Тип тела string bodyType = GetBodyTypeDescription(appearanceData.BodyType); if (!string.IsNullOrEmpty(bodyType)) { prompt.Append(bodyType); prompt.Append(", "); } // 6. Цвет кожи string skinTone = ColorDescriptionService.GetSkinToneDescription( appearanceData.SkinColor ); prompt.Append(skinTone); prompt.Append(", "); // 7. Волосы string hairDescription = GetHairDescription(appearanceData); if (!string.IsNullOrEmpty(hairDescription)) { prompt.Append(hairDescription); prompt.Append(", "); } // 8. Настроение и выражение на основе черт характера string moodDescription = GetMoodFromTraits(appearanceData.Traits); if (!string.IsNullOrEmpty(moodDescription)) { prompt.Append(moodDescription); prompt.Append(", "); } // 9. Одежда string apparelDescription = GetApparelDescription(appearanceData.Apparel); if (!string.IsNullOrEmpty(apparelDescription)) { prompt.Append(apparelDescription); prompt.Append(", "); } // 10. Качественные теги prompt.Append(GetQualityTags(settings.ArtStyle)); return prompt.ToString().Trim().TrimEnd(','); } public string GenerateNegativePrompt(StableDiffusionSettings settings) { StringBuilder negativePrompt = new StringBuilder(); // 1. Пользовательский негативный промпт (если указан) - идет первым if (!string.IsNullOrEmpty(settings.NegativePrompt)) { negativePrompt.Append(settings.NegativePrompt); negativePrompt.Append(", "); } // 2. Базовые негативные промпты negativePrompt.Append( "ugly, deformed, low quality, blurry, bad anatomy, worst quality, " ); negativePrompt.Append( "mutated, disfigured, bad proportions, extra limbs, missing limbs, " ); // 3. Специфичные для стиля негативы 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; } 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 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 apparel) { if (apparel == null || !apparel.Any()) return "simple clothes"; StringBuilder apparelDesc = new StringBuilder("wearing "); // Берем топ 5 наиболее заметных предметов одежды var visibleApparel = apparel.Take(5).ToList(); List items = new List(); 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; } } /// /// Преобразует defName в читаемый английский текст для промпта /// Пример: "Apparel_Pants" -> "pants", "Synthread" -> "synthread" /// 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(); } } }