From cdf0a5dd44af63b5530fad595ac650d5d899a848 Mon Sep 17 00:00:00 2001 From: Lemmy Date: Mon, 2 Feb 2026 21:18:22 -0500 Subject: [PATCH] template-processor: dysfunctional scheme --- Assets/Translations/de.json | 1 - Assets/Translations/en.json | 3 +- Assets/Translations/es.json | 1 - Assets/Translations/fr.json | 1 - Assets/Translations/hu.json | 1 - Assets/Translations/ja.json | 1 - Assets/Translations/ko-KR.json | 1 - Assets/Translations/nl.json | 1 - Assets/Translations/pl.json | 1 - Assets/Translations/pt.json | 1 - Assets/Translations/ru.json | 1 - Assets/Translations/sv.json | 1 - Assets/Translations/tr.json | 1 - Assets/Translations/uk-UA.json | 1 - Assets/Translations/zh-CN.json | 1 - Assets/Translations/zh-TW.json | 1 - Scripts/dev/template-processor-analysis.py | 41 ++++-- Scripts/python/src/theming/lib/image.py | 5 +- Scripts/python/src/theming/lib/palette.py | 126 ++++++++++++++++++ .../python/src/theming/template-processor.py | 10 +- Services/Theming/TemplateProcessor.qml | 4 + 21 files changed, 170 insertions(+), 34 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index f47890072..06c816e10 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -398,7 +398,6 @@ "events": "Ereignisse", "execute": "Ausführen", "faithful": "Originalgetreu", - "faithful-alt": "Treue (Alternativ)", "focus": "Fokus", "frequency": "Frequenz", "gateway": "Gateway", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index b741450ef..7c9fdb819 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -393,12 +393,12 @@ "disconnecting": "Disconnecting...", "download": "Download", "duration": "Duration", + "dysfunctional": "Dysfunctional", "edit": "Edit", "enabled": "Enabled", "events": "Events", "execute": "Execute", "faithful": "Faithful", - "faithful-alt": "Faithful (Alternate)", "focus": "Focus", "frequency": "Frequency", "gateway": "Gateway", @@ -814,6 +814,7 @@ "download-title": "Download Color Schemes", "method-description": { "content": "Material Design scheme with high-fidelity color extraction that closely matches the source content's actual colors.", + "dysfunctional": "Like Faithful but picks the second most dominant color family as primary.", "faithful": "Attempts to stay close to the source color while still generating a harmonious palette.", "fruit-salad": "Material Design scheme that produces a playful, vibrant palette with diverse and varied hues.", "monochrome": "Material Design scheme using a single-hue grayscale with minimal chromatic content.", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index d69fa5ed4..4ad02e898 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -398,7 +398,6 @@ "events": "Eventos", "execute": "Ejecutar", "faithful": "Fiel", - "faithful-alt": "Fiel (Alternativo)", "focus": "Enfoque", "frequency": "Frecuencia", "gateway": "Puerta de enlace", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 5915e0fab..04c044677 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -398,7 +398,6 @@ "events": "Événements", "execute": "Exécuter", "faithful": "Fidèle", - "faithful-alt": "Fidèle (Alternatif)", "focus": "Concentration", "frequency": "Fréquence", "gateway": "Passerelle", diff --git a/Assets/Translations/hu.json b/Assets/Translations/hu.json index d8f730f63..8deb14082 100644 --- a/Assets/Translations/hu.json +++ b/Assets/Translations/hu.json @@ -398,7 +398,6 @@ "events": "Események", "execute": "Végrehajt", "faithful": "Hű", - "faithful-alt": "Hűséges (Alternatív)", "focus": "Fókusz", "frequency": "Frekvencia", "gateway": "Átjáró", diff --git a/Assets/Translations/ja.json b/Assets/Translations/ja.json index facb47163..1adbd7d74 100644 --- a/Assets/Translations/ja.json +++ b/Assets/Translations/ja.json @@ -398,7 +398,6 @@ "events": "イベント", "execute": "実行", "faithful": "忠実", - "faithful-alt": "忠実 (代替)", "focus": "集中", "frequency": "頻度", "gateway": "ゲートウェイ", diff --git a/Assets/Translations/ko-KR.json b/Assets/Translations/ko-KR.json index c9a3c1e8e..19af8a2b4 100644 --- a/Assets/Translations/ko-KR.json +++ b/Assets/Translations/ko-KR.json @@ -398,7 +398,6 @@ "events": "일정", "execute": "실행", "faithful": "충실하게", - "faithful-alt": "충실한 (대체)", "focus": "포커스", "frequency": "빈도", "gateway": "게이트웨이", diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index a892310e7..506a5140b 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -398,7 +398,6 @@ "events": "Evenementen", "execute": "Uitvoeren", "faithful": "Getrouw", - "faithful-alt": "Trouw (Alternatief)", "focus": "Focus", "frequency": "Frequentie", "gateway": "Poort", diff --git a/Assets/Translations/pl.json b/Assets/Translations/pl.json index 41d12f654..28c27ee35 100644 --- a/Assets/Translations/pl.json +++ b/Assets/Translations/pl.json @@ -398,7 +398,6 @@ "events": "Wydarzenia", "execute": "Wykonaj", "faithful": "Wierny", - "faithful-alt": "Wierny (Alternatywny)", "focus": "Skupienie", "frequency": "Częstotliwość", "gateway": "Brama", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 776dc69ff..cad7eb485 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -398,7 +398,6 @@ "events": "Eventos", "execute": "Executar", "faithful": "Fiel", - "faithful-alt": "Fiel (Alternativo)", "focus": "Foco", "frequency": "Frequência", "gateway": "Porta de entrada", diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index 1a7916ce3..62683e644 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -398,7 +398,6 @@ "events": "События", "execute": "Выполнить", "faithful": "Верный", - "faithful-alt": "Верный (Альтернативный)", "focus": "Фокус", "frequency": "Частота", "gateway": "Шлюз", diff --git a/Assets/Translations/sv.json b/Assets/Translations/sv.json index 16194387b..c500a8ea5 100644 --- a/Assets/Translations/sv.json +++ b/Assets/Translations/sv.json @@ -398,7 +398,6 @@ "events": "Händelser", "execute": "Exekvera", "faithful": "Trogen", - "faithful-alt": "Trofast (Alternativ)", "focus": "Fokus", "frequency": "Frekvens", "gateway": "Gateway", diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index 16780770f..b604b246b 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -398,7 +398,6 @@ "events": "Etkinlikler", "execute": "Yürüt", "faithful": "Sadık", - "faithful-alt": "Sadık (Alternatif)", "focus": "Odaklanma", "frequency": "Sıklık", "gateway": "Geçit", diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index a0ae89ce5..3c5cd83f8 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -398,7 +398,6 @@ "events": "Події", "execute": "Виконати", "faithful": "Вірний", - "faithful-alt": "Вірний (Альтернативний)", "focus": "Зосередженість", "frequency": "Частота", "gateway": "Шлюз", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 5b6301a4c..ed16eae36 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -398,7 +398,6 @@ "events": "事件", "execute": "执行", "faithful": "忠实", - "faithful-alt": "忠实 (备用)", "focus": "专注", "frequency": "频率", "gateway": "网关", diff --git a/Assets/Translations/zh-TW.json b/Assets/Translations/zh-TW.json index 0bbcbb7b1..5d885e8de 100644 --- a/Assets/Translations/zh-TW.json +++ b/Assets/Translations/zh-TW.json @@ -398,7 +398,6 @@ "events": "事件", "execute": "執行", "faithful": "忠實", - "faithful-alt": "忠實 (備用)", "focus": "關注", "frequency": "頻率", "gateway": "網路閘道", diff --git a/Scripts/dev/template-processor-analysis.py b/Scripts/dev/template-processor-analysis.py index 638f6b7b3..72d0588d6 100755 --- a/Scripts/dev/template-processor-analysis.py +++ b/Scripts/dev/template-processor-analysis.py @@ -12,6 +12,7 @@ Scheme types: - M3 schemes (tonal-spot, fruit-salad, rainbow, content): Compared with matugen - vibrant: Prioritizes the most saturated colors regardless of area - faithful: Prioritizes dominant colors by area coverage +- dysfunctional: Like faithful but picks the 2nd most dominant color family - muted: Preserves hue but caps saturation low (for monochrome wallpapers) """ @@ -115,20 +116,21 @@ def run_matugen(image_path: Path, scheme: str) -> dict | None: def analyze_vibrant_faithful_muted(image_path: Path) -> None: - """Analyze vibrant, faithful, and muted mode outputs.""" + """Analyze vibrant, faithful, dysfunctional, and muted mode outputs.""" print("\n" + "=" * 78) - print("VIBRANT vs FAITHFUL vs MUTED COMPARISON") + print("VIBRANT vs FAITHFUL vs DYSFUNCTIONAL vs MUTED COMPARISON") print("=" * 78) print() - print("Vibrant: Prioritizes the most saturated colors regardless of area") - print("Faithful: Prioritizes dominant colors by area coverage") - print("Muted: Preserves hue but caps saturation low (monochrome wallpapers)") + print("Vibrant: Prioritizes the most saturated colors regardless of area") + print("Faithful: Prioritizes dominant colors by area coverage") + print("Dysfunctional: Like faithful but picks 2nd most dominant color family") + print("Muted: Preserves hue but caps saturation low (monochrome wallpapers)") print() print("-" * 78) - print(f"{'Mode':<12} {'Color':<12} {'Hex':<10} {'Hue':>8} {'Chroma':>8} {'Name':<10}") + print(f"{'Mode':<14} {'Color':<12} {'Hex':<10} {'Hue':>8} {'Chroma':>8} {'Name':<10}") print("-" * 78) - for scheme in ["vibrant", "faithful", "muted"]: + for scheme in ["vibrant", "faithful", "dysfunctional", "muted"]: colors = run_our_processor(image_path, scheme) if not colors: print(f"{scheme}: Failed to get colors") @@ -142,40 +144,51 @@ def analyze_vibrant_faithful_muted(image_path: Path) -> None: try: hct = get_hct(hex_color) name = hue_to_name(hct.hue) - print(f"{scheme:<12} {key:<12} {hex_color:<10} {hct.hue:>7.1f}° {hct.chroma:>7.1f} {name:<10}") + print(f"{scheme:<14} {key:<12} {hex_color:<10} {hct.hue:>7.1f}° {hct.chroma:>7.1f} {name:<10}") except Exception as e: - print(f"{scheme:<12} {key:<12} Error: {e}") + print(f"{scheme:<14} {key:<12} Error: {e}") print("-" * 78) # Summary comparison vibrant = run_our_processor(image_path, "vibrant") faithful = run_our_processor(image_path, "faithful") + dysfunctional = run_our_processor(image_path, "dysfunctional") muted = run_our_processor(image_path, "muted") - if vibrant and faithful and muted: + if vibrant and faithful and dysfunctional and muted: print() print("Summary:") v_hct = get_hct(vibrant.get("primary", "#000000")) f_hct = get_hct(faithful.get("primary", "#000000")) + d_hct = get_hct(dysfunctional.get("primary", "#000000")) m_hct = get_hct(muted.get("primary", "#000000")) v_name = hue_to_name(v_hct.hue) f_name = hue_to_name(f_hct.hue) + d_name = hue_to_name(d_hct.hue) m_name = hue_to_name(m_hct.hue) vf_diff = hue_diff(v_hct.hue, f_hct.hue) + fd_diff = hue_diff(f_hct.hue, d_hct.hue) - print(f" Vibrant primary: {vibrant.get('primary')} ({v_name}, hue {v_hct.hue:.0f}°, chroma {v_hct.chroma:.1f})") - print(f" Faithful primary: {faithful.get('primary')} ({f_name}, hue {f_hct.hue:.0f}°, chroma {f_hct.chroma:.1f})") - print(f" Muted primary: {muted.get('primary')} ({m_name}, hue {m_hct.hue:.0f}°, chroma {m_hct.chroma:.1f})") - print(f" V-F hue diff: {vf_diff:.1f}°") + print(f" Vibrant primary: {vibrant.get('primary')} ({v_name}, hue {v_hct.hue:.0f}°, chroma {v_hct.chroma:.1f})") + print(f" Faithful primary: {faithful.get('primary')} ({f_name}, hue {f_hct.hue:.0f}°, chroma {f_hct.chroma:.1f})") + print(f" Dysfunctional primary:{dysfunctional.get('primary')} ({d_name}, hue {d_hct.hue:.0f}°, chroma {d_hct.chroma:.1f})") + print(f" Muted primary: {muted.get('primary')} ({m_name}, hue {m_hct.hue:.0f}°, chroma {m_hct.chroma:.1f})") + print(f" V-F hue diff: {vf_diff:.1f}°") + print(f" F-D hue diff: {fd_diff:.1f}°") if vf_diff > 60: print(f" → Vibrant/Faithful picked DIFFERENT color families ({v_name} vs {f_name})") else: print(f" → Vibrant/Faithful picked SIMILAR colors") + if fd_diff > 30: + print(f" → Faithful/Dysfunctional picked DIFFERENT color families ({f_name} vs {d_name})") + else: + print(f" → Faithful/Dysfunctional picked SIMILAR colors (may only have 1 dominant family)") + # Note the muted chroma reduction if m_hct.chroma < 20: print(f" → Muted successfully reduced chroma to {m_hct.chroma:.1f}") diff --git a/Scripts/python/src/theming/lib/image.py b/Scripts/python/src/theming/lib/image.py index 4aa78291f..d18f27428 100644 --- a/Scripts/python/src/theming/lib/image.py +++ b/Scripts/python/src/theming/lib/image.py @@ -248,10 +248,11 @@ def _read_image_imagemagick(path: Path) -> list[RGB]: # -resize: downsample for performance (we don't need full resolution for color extraction) # ppm: output as PPM format (easy to parse) - # Resize to 112x112 to match matugen's color extraction + # Resize to fit within 112x112 while maintaining aspect ratio + # This matches matugen's approach and preserves color proportions # Use -filter Box for consistent results across ImageMagick versions # Use -depth 8 -colorspace sRGB -strip to reduce variance between HDRI/non-HDRI builds - resize_spec = "112x112!" + resize_spec = "112x112" try: # Try 'magick' first (ImageMagick 7+), fallback to 'convert' (ImageMagick 6) diff --git a/Scripts/python/src/theming/lib/palette.py b/Scripts/python/src/theming/lib/palette.py index 967dec02a..86dacba92 100644 --- a/Scripts/python/src/theming/lib/palette.py +++ b/Scripts/python/src/theming/lib/palette.py @@ -262,6 +262,123 @@ def _score_colors_count( return result_colors +def _family_center_hue(family: int) -> float: + """Get the center hue for a family index.""" + # Family centers based on _hue_to_family ranges: + # 0: RED (330-30°, wraps) -> center 0° + # 1: ORANGE (30-60°) -> center 45° + # 2: YELLOW (60-105°) -> center 82.5° + # 3: GREEN (105-190°) -> center 147.5° + # 4: BLUE (190-270°) -> center 230° + # 5: PURPLE (270-330°) -> center 300° + centers = [0.0, 45.0, 82.5, 147.5, 230.0, 300.0] + return centers[family] + + +def _circular_hue_diff(h1: float, h2: float) -> float: + """Calculate circular hue difference (0-180).""" + diff = abs(h1 - h2) + return min(diff, 360.0 - diff) + + +def _score_colors_dysfunctional( + colors_with_counts: list[tuple[RGB, int]], +) -> list[tuple[Color, float]]: + """ + Score colors prioritizing the 2nd most dominant hue family. + + Like count scoring but skips the dominant family (and any families + too close to it) to pick a visually distinct secondary color. + + Args: + colors_with_counts: List of (RGB, count) tuples from clustering + + Returns: + List of (Color, score) tuples, sorted by family dominance then count + """ + MIN_CHROMA = 10.0 # Filter out near-gray colors + MIN_HUE_DISTANCE = 45.0 # Minimum hue distance from dominant family + MIN_COUNT_RATIO = 0.02 # Distant family must have at least 2% of total colorful pixels + + # First pass: collect colorful colors and group by hue family + hue_families: dict[int, list[tuple[Color, float, float, int]]] = {} # family -> [(color, hue, chroma, count), ...] + + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + try: + hct = color.to_hct() + if hct.chroma >= MIN_CHROMA: + family = _hue_to_family(hct.hue) + if family not in hue_families: + hue_families[family] = [] + hue_families[family].append((color, hct.hue, hct.chroma, count)) + except (ValueError, ZeroDivisionError): + pass + + # If no colorful colors found, fall back to all colors + if not hue_families: + result = [] + for rgb, count in colors_with_counts: + color = Color.from_rgb(rgb) + result.append((color, float(count))) + result.sort(key=lambda x: -x[1]) + return result + + # Calculate total count per hue family + family_totals: list[tuple[int, int]] = [] + for family, colors in hue_families.items(): + total = sum(c[3] for c in colors) + family_totals.append((family, total)) + + # Sort families by total count (dominant family first) + family_totals.sort(key=lambda x: -x[1]) + + # Find the dominant family and its center hue + dominant_family, dominant_count = family_totals[0] + dominant_center = _family_center_hue(dominant_family) + total_colorful_pixels = sum(count for _, count in family_totals) + min_count = total_colorful_pixels * MIN_COUNT_RATIO + + # Find families that are far enough from the dominant one AND have enough pixels + distant_families = [] + close_families = [dominant_family] + for family, count in family_totals[1:]: + family_center = _family_center_hue(family) + hue_diff = _circular_hue_diff(dominant_center, family_center) + if hue_diff >= MIN_HUE_DISTANCE and count >= min_count: + distant_families.append((family, count, hue_diff)) + else: + close_families.append(family) + + # Build result: colors from distant families first + result_colors = [] + + # Sort distant families by hue distance (most different first), then by count + distant_families.sort(key=lambda x: (-x[2], -x[1])) + + for family, _, _ in distant_families: + family_colors = hue_families[family] + # Sort by count descending, chroma as tiebreaker + family_colors.sort(key=lambda x: (-x[3], -x[2])) + for color, hue, chroma, count in family_colors: + # Score encodes family rank + count for proper ordering + family_rank = next(i for i, (f, _, _) in enumerate(distant_families) if f == family) + score = (len(distant_families) - family_rank) * 1000000 + count * 1000 + chroma + result_colors.append((color, score)) + + # Add colors from close families (including dominant) at lower priority + for family in close_families: + family_colors = hue_families[family] + family_colors.sort(key=lambda x: (-x[3], -x[2])) + for color, hue, chroma, count in family_colors: + # Lower score than all distant-family colors + score = count * 1000 + chroma + result_colors.append((color, score)) + + result_colors.sort(key=lambda x: -x[1]) + return result_colors + + def _score_colors_muted( colors_with_counts: list[tuple[RGB, int]], ) -> list[tuple[Color, float]]: @@ -434,6 +551,7 @@ def extract_palette( - "population": matugen-like, representative colors (M3 schemes) - "chroma": vibrant, chroma-prioritized with centroid averaging - "count": area-dominant, picks by pixel count (faithful mode) + - "dysfunctional": picks 2nd most dominant color family - "muted": like count but without chroma filtering (monochrome wallpapers) Returns: @@ -456,6 +574,10 @@ def extract_palette( # Scoring will filter to colorful colors and pick by count cluster_count = 48 filtered = sampled + elif scoring == "dysfunctional": + # Dysfunctional mode: same as count but picks 2nd dominant family + cluster_count = 48 + filtered = sampled elif scoring == "muted": # Muted mode: similar to count but accepts low-chroma colors # For monochrome/monotonal wallpapers @@ -494,6 +616,10 @@ def extract_palette( # Use representative colors with count scoring (faithful mode) colors_for_scoring = [(c[1], c[2]) for c in clusters] scored = _score_colors_count(colors_for_scoring) + elif scoring == "dysfunctional": + # Use representative colors with dysfunctional scoring (2nd dominant family) + colors_for_scoring = [(c[1], c[2]) for c in clusters] + scored = _score_colors_dysfunctional(colors_for_scoring) elif scoring == "muted": # Use representative colors with muted scoring (no chroma filter) colors_for_scoring = [(c[1], c[2]) for c in clusters] diff --git a/Scripts/python/src/theming/template-processor.py b/Scripts/python/src/theming/template-processor.py index 7b412c012..a590b3128 100644 --- a/Scripts/python/src/theming/template-processor.py +++ b/Scripts/python/src/theming/template-processor.py @@ -12,13 +12,14 @@ Supported scheme types: - monochrome: Pure grayscale M3 scheme (chroma = 0, only error has color) - vibrant: Prioritizes the most saturated colors regardless of area coverage - faithful: Prioritizes dominant colors by area, what you see is what you get +- dysfunctional: Like faithful but picks the 2nd most dominant color family - muted: Preserves hue but caps saturation low (for monochrome/monotonal wallpapers) Usage: python3 template-processor.py IMAGE_OR_JSON [OPTIONS] Options: - --scheme-type Scheme type: tonal-spot (default), content, fruit-salad, rainbow, monochrome, vibrant, faithful, muted + --scheme-type Scheme type: tonal-spot (default), content, fruit-salad, rainbow, monochrome, vibrant, faithful, dysfunctional, muted --dark Generate dark theme only --light Generate light theme only --both Generate both themes (default) @@ -83,7 +84,7 @@ Examples: # Scheme type selection parser.add_argument( '--scheme-type', - choices=['tonal-spot', 'content', 'fruit-salad', 'rainbow', 'monochrome', 'vibrant', 'faithful', 'muted'], + choices=['tonal-spot', 'content', 'fruit-salad', 'rainbow', 'monochrome', 'vibrant', 'faithful', 'dysfunctional', 'muted'], default='tonal-spot', help='Color scheme type (default: tonal-spot)' ) @@ -267,6 +268,7 @@ def main() -> int: # This matches matugen's color extraction exactly # - vibrant: Use k-means clustering for colorful/blended colors # - faithful: Use Wu quantizer for primary (dominant by area), k-means for accents + # - dysfunctional: Like faithful but picks 2nd most dominant color family # - muted: Like count but without chroma filtering (for monochrome wallpapers) if scheme_type == "vibrant": # K-means with chroma scoring for vibrant, blended colors @@ -275,6 +277,10 @@ def main() -> int: # K-means with count scoring - picks dominant color by area coverage # This ensures primary reflects what you actually see in the image palette = extract_palette(pixels, k=5, scoring="count") + elif scheme_type == "dysfunctional": + # K-means with dysfunctional scoring - picks 2nd most dominant color family + # For when the dominant color is not what you want as primary + palette = extract_palette(pixels, k=5, scoring="dysfunctional") elif scheme_type == "muted": # K-means with muted scoring - accepts low/zero chroma colors # For monochrome/monotonal wallpapers where dominant color has low saturation diff --git a/Services/Theming/TemplateProcessor.qml b/Services/Theming/TemplateProcessor.qml index 304c5d60d..bb4d21169 100644 --- a/Services/Theming/TemplateProcessor.qml +++ b/Services/Theming/TemplateProcessor.qml @@ -51,6 +51,10 @@ Singleton { "key": "faithful", "name": I18n.tr("common.faithful") }, + { + "key": "dysfunctional", + "name": I18n.tr("common.dysfunctional") + }, { "key": "muted", "name": I18n.tr("common.color-muted")