Skip to content

Commit f9fac94

Browse files
committed
add dominant colour support and refine datamodels + fix bugs
1 parent 9904adc commit f9fac94

7 files changed

Lines changed: 101 additions & 27 deletions

File tree

nxtheme_creator/datamodels.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class ResizeMethod(Enum):
1717

1818
class ImageMode(Enum):
1919
BLUR = "blur"
20+
COLOR = "color"
2021

2122

2223
class LayoutConfig(BaseModel):

nxtheme_creator/process_image.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@
77
SIZE = (1280, 720)
88

99

10+
def _resize_max(image: Image.Image, max_size: tuple[int, int]) -> Image.Image:
11+
image_ratio = image.width / image.height
12+
target_ratio = max_size[0] / max_size[1]
13+
14+
if image_ratio > target_ratio:
15+
new_width = max_size[0]
16+
new_height = round(new_width / image_ratio)
17+
else:
18+
new_height = max_size[1]
19+
new_width = round(new_height * image_ratio)
20+
21+
return image.resize((new_width, new_height), Image.Resampling.LANCZOS)
22+
23+
1024
def resize_stretch(image: Image.Image) -> Image.Image:
1125
"""Resize the image by stretching it to the target SIZE."""
1226
return image.resize(SIZE, Image.Resampling.LANCZOS)
@@ -33,16 +47,43 @@ def resize_center_crop(image: Image.Image) -> Image.Image:
3347

3448

3549
def resize_outer_crop_letterbox(image: Image.Image) -> Image.Image:
36-
"""Resize the image using outer crop (letterbox) method."""
37-
# Add padding if necessary ,,letterbox,,
38-
image.thumbnail(SIZE, Image.Resampling.LANCZOS)
39-
letterbox_image = ImageOps.pad(image, SIZE, color=(0, 0, 0))
40-
return letterbox_image
50+
"""Resize the image using outer crop with blurred background."""
51+
# Resize image to fit in target size, preserving aspect ratio
52+
fg = _resize_max(image, SIZE)
53+
54+
# Create a blurred background from the original image
55+
bg = image.resize(SIZE, Image.Resampling.LANCZOS)
56+
bg = bg.filter(ImageFilter.GaussianBlur(radius=20))
57+
58+
# Paste the resized image onto the center of the blurred background
59+
offset = (
60+
(SIZE[0] - fg.width) // 2,
61+
(SIZE[1] - fg.height) // 2
62+
)
63+
bg.paste(fg, offset)
64+
return bg
65+
4166

4267

4368
def mode_blur(image: Image.Image) -> Image.Image:
4469
return image.filter(ImageFilter.GaussianBlur(radius=20))
4570

71+
def mode_dominant_color(image: Image.Image, colors:int=8):
72+
quantized = image.convert("RGB").quantize(colors=colors)
73+
palette = quantized.getpalette()
74+
color_counts = quantized.getcolors()
75+
76+
# Extract RGB tuples
77+
rgb_palette = [
78+
((palette[i * 3], palette[i * 3 + 1], palette[i * 3 + 2]), count)
79+
for count, i in color_counts
80+
]
81+
82+
# Return the color with the highest count
83+
dominant=max(rgb_palette, key=lambda x: x[1])[0]
84+
return Image.new("RGB", SIZE, dominant)
85+
86+
4687

4788
def resize_image(
4889
input_path: str,
@@ -63,6 +104,8 @@ def resize_image(
63104
# Apply effects here
64105
if image_mode == ImageMode.BLUR:
65106
transformed_image = mode_blur(transformed_image)
107+
if image_mode == ImageMode.COLOR:
108+
transformed_image = mode_dominant_color(transformed_image)
66109

67110
transformed_image.save(output_path, progressive=False)
68111
return output_path

nxtheme_creator/process_themes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def resolveConf(nxthemebin: str | None, conf: Config) -> Config:
111111
layouts_dir = Path(nxthemebin).parent / "Layouts"
112112
else:
113113
layouts_dir = THISDIR / "layouts"
114+
_conf = {"author_name": conf.author_name, "resize_method": conf.resize_method}
114115

115116
for screen_type in SCREEN_TYPES:
116117
layout_config: LayoutConfig = getattr(conf, screen_type, None)
@@ -137,7 +138,6 @@ def resolveConf(nxthemebin: str | None, conf: Config) -> Config:
137138
msg = f"{conf[screen_type]} or {layout} does not exist :("
138139
raise RuntimeError(msg)
139140

140-
_conf = {"author_name": conf.author_name, "resize_method": conf.resize_method}
141141

142142
_conf[screen_type] = LayoutConfig(layout=str(layout), mode=layout_config.mode)
143143
return Config.model_validate(_conf)
@@ -215,3 +215,5 @@ def processImages(nxthemebin: str | None, inputdir: str, outputdir: str, config:
215215
author_name=author_name,
216216
out=out,
217217
)
218+
219+
return config.model_dump()

tests/data/center_crop/example.jpg

37 KB
Loading

tests/data/outer_crop/example.jpg

30.9 KB
Loading

tests/data/stretch/example.jpg

37 KB
Loading

tests/test_compat_1100.py

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,29 +50,57 @@
5050
SWITCH_THEMES_EXE = r"C:\Users\Dell\Downloads\Release4.8.1\SwitchThemes.exe"
5151

5252

53-
def test_me():
54-
input_dir = str(THISDIR / "data/input")
55-
output_dir = str(THISDIR / "data/output")
56-
57-
home_layout_name = random.choice([p.name for p in (nxdir / "layouts/home").iterdir()])
58-
lock_layout_name = random.choice([p.name for p in (nxdir / "layouts/lock").iterdir()])
59-
apps_layout_name = random.choice([p.name for p in (nxdir / "layouts/apps").iterdir()])
60-
psl_layout_name = random.choice([p.name for p in (nxdir / "layouts/psl").iterdir()])
61-
62-
config = Config.model_validate(
63-
{
64-
"home": home_layout_name,
65-
"lock": lock_layout_name,
66-
"apps": apps_layout_name,
67-
"psl": psl_layout_name,
68-
"author_name": "JohnDoe",
69-
"resize_method": "stretch",
70-
}
71-
)
53+
def test_centerCrop():
54+
conf = {
55+
"home": aux_rand_layout("home"),
56+
# "lock": aux_rand_layout("lock"),
57+
"apps": aux_rand_layout("apps"),
58+
"psl": aux_rand_layout("psl"),
59+
"author_name": "JohnDoe",
60+
"resize_method": "centerCrop",
61+
}
62+
aux_testcase("center_crop", conf)
63+
64+
65+
def test_stretch():
66+
conf = {
67+
"home": {"layout": aux_rand_layout("home"), "mode": "color"},
68+
# "lock": aux_rand_layout("lock"),
69+
"apps": aux_rand_layout("apps"),
70+
"psl": aux_rand_layout("psl"),
71+
"author_name": "JohnDoe",
72+
"resize_method": "stretch",
73+
}
74+
aux_testcase("stretch", conf)
75+
76+
77+
def test_outerCrop():
78+
conf = {
79+
"home": {"layout": aux_rand_layout("home"), "mode": "blur"},
80+
# "lock": aux_rand_layout("lock"),
81+
"apps": aux_rand_layout("apps"),
82+
"psl": aux_rand_layout("psl"),
83+
"author_name": "JohnDoe",
84+
"resize_method": "outerCrop",
85+
}
86+
aux_testcase("outer_crop", conf)
87+
88+
89+
def aux_rand_layout(layout: str) -> str:
90+
return random.choice([p.name for p in (nxdir / "layouts" / layout).iterdir()])
91+
92+
93+
def aux_testcase(_input_dir: str, config: dict):
94+
input_dir = str(THISDIR / "data" / _input_dir)
95+
output_dir = str(THISDIR / "data/output" / _input_dir)
96+
97+
config = Config.model_validate(config)
7298

7399
# 'Native" with nxtheme-creator
74-
processImages(
75-
nxthemebin=None, inputdir=input_dir, outputdir=output_dir + "_native", config=config
100+
print(
101+
processImages(
102+
nxthemebin=None, inputdir=input_dir, outputdir=output_dir + "_native", config=config
103+
)
76104
)
77105

78106
# # With SWITCH_THEMES_EXE

0 commit comments

Comments
 (0)