Skip to content

Commit 24effb4

Browse files
committed
adds get_closest_aspect_ratio node
1 parent 7f2086e commit 24effb4

2 files changed

Lines changed: 135 additions & 0 deletions

File tree

griptape_nodes_library.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,6 +1781,17 @@
17811781
"group": "edit"
17821782
}
17831783
},
1784+
{
1785+
"class_name": "GetClosestAspectRatio",
1786+
"file_path": "griptape_nodes_library/image/get_closest_aspect_ratio.py",
1787+
"metadata": {
1788+
"category": "image",
1789+
"description": "Pick the candidate aspect ratio whose numeric value is nearest to the target",
1790+
"display_name": "Get Closest Aspect Ratio",
1791+
"icon": "ratio",
1792+
"group": "edit"
1793+
}
1794+
},
17841795
{
17851796
"class_name": "ImageDetails",
17861797
"file_path": "griptape_nodes_library/image/image_details.py",
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from collections.abc import Iterable
2+
from typing import Any
3+
4+
from griptape_nodes.exe_types.core_types import ParameterMode
5+
from griptape_nodes.exe_types.node_types import DataNode
6+
from griptape_nodes.exe_types.param_types.parameter_string import ParameterString
7+
8+
9+
def closest_aspect_ratio(target: str, aspect_ratios: list[str]) -> str:
10+
def ratio_to_float(ratio: str) -> float:
11+
w, h = map(float, ratio.split(":"))
12+
return w / h
13+
14+
target_value = ratio_to_float(target)
15+
closest = min(aspect_ratios, key=lambda r: abs(ratio_to_float(r) - target_value))
16+
return closest
17+
18+
19+
def _is_valid_aspect_ratio_string(value: str) -> bool:
20+
try:
21+
parts = value.split(":")
22+
if len(parts) != 2:
23+
return False
24+
_, height = map(float, parts)
25+
if height == 0:
26+
return False
27+
except ValueError:
28+
return False
29+
return True
30+
31+
32+
def _normalized_valid_target(aspect_ratio_value: Any) -> str | None:
33+
if aspect_ratio_value is None:
34+
return None
35+
if not isinstance(aspect_ratio_value, str):
36+
return None
37+
target = aspect_ratio_value.strip()
38+
if not target:
39+
return None
40+
if not _is_valid_aspect_ratio_string(target):
41+
return None
42+
return target
43+
44+
45+
def _valid_aspect_ratio_candidates(raw_items: Iterable[Any]) -> list[str]:
46+
result: list[str] = []
47+
for item in raw_items:
48+
if not isinstance(item, str):
49+
continue
50+
candidate = item.strip()
51+
if not candidate:
52+
continue
53+
if not _is_valid_aspect_ratio_string(candidate):
54+
continue
55+
result.append(candidate)
56+
return result
57+
58+
59+
def _aspect_ratio_list_from_value(raw: Any) -> list[str]:
60+
"""Build candidate aspect ratios from a delimited string or an iterable of strings."""
61+
if raw is None:
62+
return []
63+
if isinstance(raw, str):
64+
items: list[str] = []
65+
for line in raw.splitlines():
66+
for piece in line.split(","):
67+
s = piece.strip()
68+
if s:
69+
items.append(s)
70+
return _valid_aspect_ratio_candidates(items)
71+
if isinstance(raw, Iterable):
72+
return _valid_aspect_ratio_candidates(raw)
73+
return []
74+
75+
76+
class GetClosestAspectRatio(DataNode):
77+
"""Pick the candidate aspect ratio whose numeric value is nearest to the target."""
78+
79+
def __init__(self, name: str, metadata: dict[Any, Any] | None = None) -> None:
80+
super().__init__(name, metadata)
81+
82+
self.add_parameter(
83+
ParameterString(
84+
name="aspect_ratio",
85+
allowed_modes={ParameterMode.INPUT, ParameterMode.PROPERTY},
86+
default_value="16:9",
87+
tooltip="Target aspect ratio as width:height (e.g. 16:9)",
88+
placeholder_text="16:9",
89+
)
90+
)
91+
92+
self.add_parameter(
93+
ParameterString(
94+
name="aspect_ratios",
95+
default_value="1:1, 3:2, 2:3, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9",
96+
tooltip=(
97+
"Comma or newline separated candidate aspect ratios "
98+
"(e.g. 1:1, 3:2, 2:3 or one ratio per line)"
99+
),
100+
)
101+
)
102+
103+
self.add_parameter(
104+
ParameterString(
105+
name="closest_aspect_ratio",
106+
allowed_modes={ParameterMode.OUTPUT},
107+
default_value="",
108+
placeholder_text="The closest aspect ratio",
109+
tooltip="The candidate closest to the target by absolute difference in width/height ratio",
110+
)
111+
)
112+
113+
def process(self) -> None:
114+
target = _normalized_valid_target(self.get_parameter_value("aspect_ratio"))
115+
if target is None:
116+
self.parameter_output_values["closest_aspect_ratio"] = ""
117+
return
118+
119+
candidates = _aspect_ratio_list_from_value(self.get_parameter_value("aspect_ratios"))
120+
if not candidates:
121+
self.parameter_output_values["closest_aspect_ratio"] = ""
122+
return
123+
124+
self.parameter_output_values["closest_aspect_ratio"] = closest_aspect_ratio(target, candidates)

0 commit comments

Comments
 (0)