scheme setter, light mode?
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Optional
|
||||
import typer
|
||||
import json
|
||||
|
||||
@@ -14,22 +14,21 @@ app = typer.Typer()
|
||||
|
||||
@app.command()
|
||||
def generate(
|
||||
path: Annotated[
|
||||
Path,
|
||||
typer.Option(),
|
||||
],
|
||||
output: Annotated[
|
||||
Path,
|
||||
typer.Option(),
|
||||
],
|
||||
thumbnail: Annotated[
|
||||
Path,
|
||||
typer.Option(),
|
||||
],
|
||||
scheme: Annotated[
|
||||
str,
|
||||
typer.Option()
|
||||
]
|
||||
# image inputs (optional - used for image mode)
|
||||
image_path: Optional[Path] = typer.Option(
|
||||
None, help="Path to source image. Required for image mode."),
|
||||
thumbnail_path: Optional[Path] = typer.Option(
|
||||
Path("thumb.jpg"), help="Path to temporary thumbnail (image mode)."),
|
||||
scheme: Optional[str] = typer.Option(
|
||||
"fruit-salad", help="Color scheme algorithm to use for image mode. Ignored in preset mode."),
|
||||
# preset inputs (optional - used for preset mode)
|
||||
preset: Optional[str] = typer.Option(
|
||||
None, help="Name of a premade scheme (preset)."),
|
||||
flavour: str = typer.Option("default", help="Flavor of the preset scheme."),
|
||||
mode: str = typer.Option(
|
||||
"dark", help="Mode of the preset scheme (dark or light)."),
|
||||
# output (required)
|
||||
output: Path = typer.Option(..., help="Output JSON path.")
|
||||
):
|
||||
match scheme:
|
||||
case "fruit-salad":
|
||||
@@ -53,6 +52,47 @@ def generate(
|
||||
case _:
|
||||
from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad as Scheme
|
||||
|
||||
def normalize_hex(s: str) -> str:
|
||||
# Accepts "6a73ac", "#6a73ac", "B5CCBA" and returns "#B5CCBA"
|
||||
s = s.strip()
|
||||
if s.startswith("#"):
|
||||
s = s[1:]
|
||||
# If the value contains alpha (8 chars), drop alpha and keep RGB (last 6).
|
||||
if len(s) == 8:
|
||||
s = s[2:] # assume AARRGGBB -> drop AA
|
||||
if len(s) != 6:
|
||||
raise ValueError(
|
||||
f"Invalid hex color '{s}' (expected 6 hex digits, optionally prefixed with '#').")
|
||||
return f"#{s.upper()}"
|
||||
|
||||
def parse_preset_file(file_path: Path) -> dict:
|
||||
"""
|
||||
Parse a preset scheme file with lines like:
|
||||
primary_paletteKeyColor 6a73ac
|
||||
background 131317
|
||||
onBackground e4e1e7
|
||||
Returns a dict mapping key -> "#RRGGBB"
|
||||
"""
|
||||
mapping = {}
|
||||
with file_path.open("r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = line.split()
|
||||
if len(parts) < 2:
|
||||
# ignore malformed lines
|
||||
continue
|
||||
key = parts[0].strip()
|
||||
value = parts[1].strip()
|
||||
try:
|
||||
mapping[key] = normalize_hex(value)
|
||||
except ValueError:
|
||||
# skip invalid hex but log to stdout
|
||||
print(
|
||||
f"Warning: skipping invalid color value for '{key}': {value}")
|
||||
return mapping
|
||||
|
||||
def generate_thumbnail(image_path, thumbnail_path, size=(128, 128)):
|
||||
thumbnail_file = Path(thumbnail_path)
|
||||
|
||||
@@ -101,13 +141,52 @@ def generate(
|
||||
with open(output_file, "w") as f:
|
||||
json.dump(output_dict, f, indent=4)
|
||||
|
||||
def generate_color_scheme_from_preset(preset_mapping: dict, output_path: Path, preset_name: str, flavour: str):
|
||||
"""
|
||||
Build JSON output using keys from the preset file.
|
||||
Any keys in preset_mapping are included verbatim in the "colors" object.
|
||||
"""
|
||||
color_dict = {}
|
||||
for k, v in preset_mapping.items():
|
||||
color_dict[k] = v # v already normalized to "#RRGGBB"
|
||||
|
||||
output_dict = {
|
||||
"name": preset_name,
|
||||
"flavour": flavour,
|
||||
"mode": mode, # could be made configurable / auto-detected
|
||||
"variant": scheme,
|
||||
"colors": color_dict
|
||||
}
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with output_path.open("w", encoding="utf-8") as f:
|
||||
json.dump(output_dict, f, indent=4)
|
||||
|
||||
def int_to_hex(argb_int):
|
||||
return "#{:06X}".format(argb_int & 0xFFFFFF)
|
||||
|
||||
try:
|
||||
generate_thumbnail(path, str(thumbnail))
|
||||
generate_color_scheme(str(thumbnail), output)
|
||||
if preset:
|
||||
# try local presets directory: presets/<preset>/<flavour>.txt or presets/<preset>-<flavour>.txt
|
||||
base1 = Path("zshell") / "assets" / "schemes" / \
|
||||
preset / flavour / f"{mode}.txt"
|
||||
if base1.exists():
|
||||
preset_path = base1
|
||||
else:
|
||||
raise FileNotFoundError(
|
||||
f"Preset file not found. Looked for: {base1.resolve()}. "
|
||||
"You can also pass --preset-file <path> directly."
|
||||
)
|
||||
|
||||
mapping = parse_preset_file(preset_path)
|
||||
generate_color_scheme_from_preset(
|
||||
mapping, output, preset or preset_path.stem, flavour)
|
||||
typer.echo(f"Wrote preset-based scheme to {output}")
|
||||
raise typer.Exit()
|
||||
|
||||
generate_thumbnail(image_path, str(thumbnail_path))
|
||||
generate_color_scheme(str(thumbnail_path), output)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
with open(output, "w") as f:
|
||||
f.write(f"Error: {e}")
|
||||
# with open(output, "w") as f:
|
||||
# f.write(f"Error: {e}")
|
||||
|
||||
Reference in New Issue
Block a user