check point

This commit is contained in:
dimitar committed 2026-05-22 03:51:50 +02:00
1 parent 33c70e776a
commit b0f9acdb47
37 files changed
+4469 -177

No files matched your search

+18
View File
@@ -161,3 +161,21 @@ auto_place_panel_bubbles :: proc(panel: Panel, panel_w, panel_h: f32) -> []Speec
return bubbles[:]
}
update_bubble_text :: proc(bubble: Speech_Bubble, new_text: string) -> Speech_Bubble {
updated := bubble
updated.text = new_text
return updated
}
update_bubble_position :: proc(bubble: Speech_Bubble, x, y: f32) -> Speech_Bubble {
updated := bubble
updated.position = Position{x = x, y = y}
return updated
}
reset_bubble_position :: proc(bubble: Speech_Bubble) -> Speech_Bubble {
updated := bubble
updated.position = Position{x = 0, y = 0}
return updated
}
+233
View File
@@ -0,0 +1,233 @@
package core
import "core:strings"
import "core:fmt"
CHARACTER_TEMPLATE_STRING :: "{age}-year-old {gender}, {hairColor} {hairStyle} hair, {eyeColor} eyes, {skinTone} skin, {bodyType} build, wearing {outfit}, {accessories}, {distinguishingFeatures}"
extract_age :: proc(description: string) -> string {
lower := strings.to_lower(description)
defer delete(lower)
// Try digit patterns first
i: int = 0
for i < len(lower)-3 {
if lower[i] >= '0' && lower[i] <= '9' {
start := i
for i < len(lower) && lower[i] >= '0' && lower[i] <= '9' {
i += 1
}
rest := lower[i:]
if strings.has_prefix(rest, "-year-old") || strings.has_prefix(rest, "-year") || strings.has_prefix(rest, " years") || strings.has_prefix(rest, " year") {
return strings.clone(lower[start:i])
}
}
i += 1
}
// Try word patterns
word_ages := []string{"young", "teen", "adult", "middle-aged", "middle aged", "elderly", "old"}
for wa in word_ages {
if strings.contains(lower, wa) {
return strings.clone(wa)
}
}
return ""
}
extract_gender :: proc(description: string) -> string {
lower := strings.to_lower(description)
defer delete(lower)
male_terms := []string{"male", "man", "boy", "guy", "he", "him"}
female_terms := []string{"female", "woman", "girl", "lady", "she", "her"}
nb_terms := []string{"non-binary", "nonbinary", "nb", "enby"}
for t in nb_terms {
if strings.contains(lower, t) { return strings.clone("non-binary") }
}
for t in female_terms {
if strings.contains(lower, t) { return strings.clone("female") }
}
for t in male_terms {
if strings.contains(lower, t) { return strings.clone("male") }
}
return ""
}
extract_hair_color :: proc(description: string) -> string {
lower := strings.to_lower(description)
defer delete(lower)
colors := []string{"black", "brown", "blonde", "blond", "red", "auburn", "brunette", "silver", "gray", "grey", "white", "blue", "green", "purple", "pink", "orange", "golden", "platinum", "chestnut", "strawberry"}
for c in colors {
if strings.contains(lower, c) {
return strings.clone(c)
}
}
return ""
}
extract_hair_style :: proc(description: string) -> string {
lower := strings.to_lower(description)
defer delete(lower)
styles := []string{"long", "short", "medium", "curly", "straight", "wavy", "spiky", "spikey", "ponytail", "bun", "braid", "buzz cut", "bald", "mohawk", "pixie", "bob", "afro", "dreadlocks", "dreads", "crew cut", "layered"}
for s in styles {
if strings.contains(lower, s) {
return strings.clone(s)
}
}
return ""
}
extract_eye_color :: proc(description: string) -> string {
lower := strings.to_lower(description)
defer delete(lower)
colors := []string{"blue", "brown", "green", "hazel", "amber", "gray", "grey", "violet", "purple", "black", "red"}
for c in colors {
if strings.contains(lower, c) {
return strings.clone(c)
}
}
return ""
}
extract_skin_tone :: proc(description: string) -> string {
lower := strings.to_lower(description)
defer delete(lower)
tones := []string{"fair", "pale", "light", "medium", "tan", "olive", "brown", "dark", "ebony", "porcelain", "bronze", "caramel", "warm", "cool"}
for t in tones {
if strings.contains(lower, t) {
return strings.clone(t)
}
}
return ""
}
extract_body_type :: proc(description: string) -> string {
lower := strings.to_lower(description)
defer delete(lower)
types := []string{"slim", "skinny", "thin", "average", "athletic", "muscular", "heavy", "large", "tall", "short", "petite", "stocky", "lean", "broad", "curvy", "slender", "chubby", "fit"}
for t in types {
if strings.contains(lower, t) {
return strings.clone(t)
}
}
return ""
}
extract_outfit :: proc(description: string) -> string {
lower := strings.to_lower(description)
// Try "wearing X" pattern
if idx := strings.index(lower, "wearing "); idx >= 0 {
rest := lower[idx+len("wearing "):]
end := len(rest)
if comma := strings.index(rest, ","); comma >= 0 { end = comma }
if period := strings.index(rest, "."); period >= 0 && period < end { end = period }
return strings.clone(rest[:end])
}
// Try "dressed in X" pattern
if idx := strings.index(lower, "dressed in "); idx >= 0 {
rest := lower[idx+len("dressed in "):]
end := len(rest)
if comma := strings.index(rest, ","); comma >= 0 { end = comma }
return strings.clone(rest[:end])
}
return ""
}
extract_accessories :: proc(description: string) -> string {
lower := strings.to_lower(description)
defer delete(lower)
accessories := []string{"glasses", "sunglasses", "hat", "cap", "beanie", "scarf", "necklace", "pendant", "earrings", "bracelet", "watch", "ring", "backpack", "bag", "gloves", "belt", "tie", "bow", "crown", "headband"}
found: [dynamic]string
for a in accessories {
if strings.contains(lower, a) {
append(&found, a)
}
}
if len(found) == 0 {
return ""
}
result := strings.join(found[:], ", ")
defer delete(found)
return result
}
extract_distinguishing_features :: proc(description: string) -> string {
lower := strings.to_lower(description)
defer delete(lower)
features := []string{"scar", "scars", "tattoo", "tattoos", "freckles", "mole", "birthmark", "beard", "mustache", "goatee", "monocle", "prosthetic", "piercing", "piercings", "dimples", "cleft chin"}
found: [dynamic]string
for f in features {
if strings.contains(lower, f) {
append(&found, f)
}
}
if len(found) == 0 {
return ""
}
result := strings.join(found[:], ", ")
defer delete(found)
return result
}
parse_description_to_template :: proc(description: string) -> Character_Prompt_Template {
return Character_Prompt_Template{
age = extract_age(description),
gender = extract_gender(description),
hair_color = extract_hair_color(description),
hair_style = extract_hair_style(description),
eye_color = extract_eye_color(description),
skin_tone = extract_skin_tone(description),
body_type = extract_body_type(description),
outfit = extract_outfit(description),
accessories = extract_accessories(description),
distinguishing_features = extract_distinguishing_features(description),
}
}
extract_color_palette :: proc(description: string) -> Color_Palette {
return Color_Palette{
hair = extract_hair_color(description),
eyes = extract_eye_color(description),
skin = extract_skin_tone(description),
outfit = extract_outfit(description),
}
}
template_to_string :: proc(t: Character_Prompt_Template) -> string {
parts: [dynamic]string
if len(t.age) > 0 { append(&parts, fmt.tprintf("%s-year-old", t.age)) }
if len(t.gender) > 0 { append(&parts, t.gender) }
if len(t.hair_color) > 0 || len(t.hair_style) > 0 {
hair := ""
if len(t.hair_color) > 0 { hair = t.hair_color }
if len(t.hair_style) > 0 {
if len(hair) > 0 { hair = fmt.tprintf("%s %s", hair, t.hair_style) }
else { hair = t.hair_style }
}
append(&parts, fmt.tprintf("%s hair", hair))
}
if len(t.eye_color) > 0 { append(&parts, fmt.tprintf("%s eyes", t.eye_color)) }
if len(t.skin_tone) > 0 { append(&parts, fmt.tprintf("%s skin", t.skin_tone)) }
if len(t.body_type) > 0 { append(&parts, fmt.tprintf("%s build", t.body_type)) }
if len(t.outfit) > 0 { append(&parts, fmt.tprintf("wearing %s", t.outfit)) }
if len(t.accessories) > 0 { append(&parts, t.accessories) }
if len(t.distinguishing_features) > 0 { append(&parts, t.distinguishing_features) }
result := strings.join(parts[:], ", ")
defer delete(parts)
return result
}
-1
View File
@@ -46,7 +46,6 @@ dispose_script_owned :: proc(script: ^Comic_Script) {
for d in pan.dialogue {
delete(d.speaker_id)
delete(d.text)
delete(d.emotion)
}
delete(pan.dialogue)
for s in pan.sound_effects { delete(s) }
+95
View File
@@ -0,0 +1,95 @@
package core
import "core:strings"
QUALITY_MODIFIER :: "high quality, detailed, clean lines, vibrant colors, professional illustration, best quality, masterpiece"
NEGATIVE_PROMPT_CHARACTER :: "blurry, low quality, distorted face, extra limbs, bad anatomy, deformed, watermark, signature"
NEGATIVE_PROMPT_PANEL :: "blurry, low quality, distorted face, extra limbs, bad anatomy, deformed, watermark, signature, text, speech bubble"
ART_STYLE_KEYWORDS :: [8]string{
"Japanese manga style, black and white ink, screentone shading, speed lines, dramatic shadows, clean linework, right-to-left reading",
"American comic book style, bold outlines, vibrant colors, dynamic poses, cross-hatching, Ben-Day dots, left-to-right reading",
"16-bit pixel art style, retro game aesthetic, limited color palette, crisp pixels, dithering, nostalgic",
"watercolor painting style, soft edges, paper texture, translucent washes, delicate linework, artistic",
"film noir style, high contrast black and white, dramatic chiaroscuro lighting, gritty, shadow-heavy, 1940s aesthetic",
"chibi anime style, super deformed characters, big heads, small bodies, cute, colorful, expressive",
"pencil sketch style, rough linework, cross-hatching, construction lines, draft aesthetic, monochrome",
"cyberpunk aesthetic, neon lighting, futuristic cityscapes, holographic elements, high contrast, synthwave colors",
}
get_style_keywords :: proc(key: Art_Style_Key) -> string {
keywords := ART_STYLE_KEYWORDS
return keywords[int(key)]
}
parse_art_style_key :: proc(raw: string) -> Art_Style_Key {
lower := strings.to_lower(raw)
if strings.contains(lower, "manga") { return .Manga }
if strings.contains(lower, "western") || strings.contains(lower, "american") || strings.contains(lower, "comic") { return .Western_Comic }
if strings.contains(lower, "pixel") || strings.contains(lower, "retro") || strings.contains(lower, "8-bit") || strings.contains(lower, "16-bit") { return .Pixel_Art }
if strings.contains(lower, "watercolor") || strings.contains(lower, "watercolour") { return .Watercolor }
if strings.contains(lower, "noir") || strings.contains(lower, "film noir") { return .Noir }
if strings.contains(lower, "chibi") || strings.contains(lower, "super deformed") { return .Chibi }
if strings.contains(lower, "sketch") || strings.contains(lower, "pencil") || strings.contains(lower, "draw") { return .Sketch }
if strings.contains(lower, "cyberpunk") || strings.contains(lower, "neon") || strings.contains(lower, "synthwave") { return .Cyberpunk }
return .Western_Comic
}
Sdxl_Image_Size :: enum {
Square,
Landscape_16_9,
Landscape_4_3,
Portrait_4_3,
Square_HD,
Portrait_16_9,
}
sdxl_size_name :: proc(s: Sdxl_Image_Size) -> string {
switch s {
case .Square: return "square"
case .Landscape_16_9: return "landscape_16_9"
case .Landscape_4_3: return "landscape_4_3"
case .Portrait_4_3: return "portrait_4_3"
case .Square_HD: return "square_hd"
case .Portrait_16_9: return "portrait_16_9"
}
return "landscape_4_3"
}
get_image_size_for_shot_type :: proc(shot: Shot_Type) -> Sdxl_Image_Size {
switch shot {
case .Establishing, .Wide, .Aerial:
return .Landscape_16_9
case .Medium, .Over_Shoulder:
return .Landscape_4_3
case .Close_Up:
return .Portrait_4_3
case .Extreme_Close_Up:
return .Square_HD
}
return .Landscape_4_3
}
emotion_name :: proc(e: Emotion) -> string {
switch e {
case .Happy: return "happy"
case .Sad: return "sad"
case .Angry: return "angry"
case .Surprised: return "surprised"
case .Neutral: return "neutral"
case .Determined: return "determined"
}
return "neutral"
}
parse_emotion :: proc(raw: string) -> Emotion {
lower := strings.to_lower(raw)
if strings.contains(lower, "happy") || strings.contains(lower, "joy") || strings.contains(lower, "smile") { return .Happy }
if strings.contains(lower, "sad") || strings.contains(lower, "cry") || strings.contains(lower, "sorrow") { return .Sad }
if strings.contains(lower, "angry") || strings.contains(lower, "rage") || strings.contains(lower, "furious") { return .Angry }
if strings.contains(lower, "surpris") || strings.contains(lower, "shock") || strings.contains(lower, "amazed") { return .Surprised }
if strings.contains(lower, "determin") || strings.contains(lower, "resolv") || strings.contains(lower, "focused") { return .Determined }
return .Neutral
}
+24
View File
@@ -54,3 +54,27 @@ normalize_script :: proc(script: Comic_Script) -> Comic_Script {
return normalized
}
count_character_appearances :: proc(script: ^Comic_Script) {
for i in 0..<len(script.characters) {
char_id := script.characters[i].id
count := 0
first_panel := ""
for page_idx in 0..<len(script.pages) {
for panel_idx in 0..<len(script.pages[page_idx].panels) {
panel := &script.pages[page_idx].panels[panel_idx]
for cid in panel.characters_present {
if cid == char_id {
count += 1
if len(first_panel) == 0 {
first_panel = panel.panel_id
}
break
}
}
}
}
script.characters[i].appearance_count = count
script.characters[i].first_appearance_panel = first_panel
}
}
+21 -1
View File
@@ -70,6 +70,15 @@ Bubble_Type :: enum {
Sound_Effect,
}
Emotion :: enum {
Happy,
Sad,
Angry,
Surprised,
Neutral,
Determined,
}
Transition_Type :: enum {
None,
Fade,
@@ -78,6 +87,17 @@ Transition_Type :: enum {
Action_Lines,
}
Art_Style_Key :: enum {
Manga,
Western_Comic,
Pixel_Art,
Watercolor,
Noir,
Chibi,
Sketch,
Cyberpunk,
}
Position :: struct {
x: f32,
y: f32,
@@ -126,7 +146,7 @@ Dialogue :: struct {
speaker_id: string,
text: string,
bubble_type: Bubble_Type,
emotion: string,
emotion: Emotion,
}
Panel :: struct {