ImagePaletteExtractor: change outputs to JSON-only (custom_json); update tests

This commit is contained in:
TJ16th
2025-11-10 02:51:28 +09:00
parent dc629e25c7
commit 54b283a744
3 changed files with 32 additions and 11 deletions
+6 -7
View File
@@ -125,9 +125,8 @@ def _hex_from_rgb01(rgb: np.ndarray) -> str:
class ImagePaletteExtractor:
"""
Extract 8 representative colors from an IMAGE and output:
- custom_json: {"colors":["#RRGGBB", ...]} (8 colors)
- color_0 .. color_7: each #RRGGBB
Extract 8 representative colors from an IMAGE and output only a JSON string:
custom_json = {"colors":["#RRGGBB", ...]} (8 colors)
"""
@classmethod
@@ -142,8 +141,8 @@ class ImagePaletteExtractor:
}
}
RETURN_TYPES = tuple(["STRING"] * 9)
RETURN_NAMES = ("custom_json",) + tuple([f"color_{i}" for i in range(8)])
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("custom_json",)
FUNCTION = "extract_palette"
CATEGORY = "TJnodes/color"
@@ -242,7 +241,7 @@ class ImagePaletteExtractor:
order = np.argsort(-counts) # frequency
return centers_rgb[order], counts[order]
def extract_palette(self, image, sample_max_pixels: int = 100_000, merge_delta: float = 6.0, seed: int = 42, sort: str = "frequency") -> Tuple[str, str, str, str, str, str, str, str, str]:
def extract_palette(self, image, sample_max_pixels: int = 100_000, merge_delta: float = 6.0, seed: int = 42, sort: str = "frequency") -> Tuple[str]:
arr = self._to_numpy_rgb01(image)
samples = self._downsample_or_sample(arr, sample_max_pixels, seed)
lab = _rgb_to_lab(samples)
@@ -263,4 +262,4 @@ class ImagePaletteExtractor:
while len(hexes) < k:
hexes.append("#000000")
custom_json = json.dumps({"colors": hexes}, ensure_ascii=False)
return (custom_json, *hexes[:k])
return (custom_json,)
+19
View File
@@ -123,6 +123,25 @@ app.registerExtension({
const r = onDrawForeground?.apply(this, arguments);
const ui = attachPaletteUI(this);
positionChips(this, ui.container);
// custom の場合は custom_json を毎フレーム監視して即時更新
const presetWidget = this.widgets?.find(w => w.name === 'preset');
const customWidget = this.widgets?.find(w => w.name === 'custom_json');
if (presetWidget && presetWidget.value === 'custom' && customWidget) {
const currentText = customWidget.value || '';
if (this.__last_custom_text__ !== currentText) {
this.__last_custom_text__ = currentText;
const colors = parseCustomColors(currentText);
if (colors) {
this.__palette_colors__ = colors;
setErrorVisual(this, false);
} else {
this.__palette_colors__ = null;
setErrorVisual(this, !!currentText.trim());
}
}
}
updateChips(this, ui.chips);
return r;
};
+7 -4
View File
@@ -36,12 +36,15 @@ class TestImagePaletteExtractor(unittest.TestCase):
node = ImagePaletteExtractor()
image = self._make_test_image()
out = node.extract_palette(image, sample_max_pixels=50_000, seed=1)
self.assertEqual(len(out), 9)
self.assertEqual(len(out), 1)
custom_json = out[0]
colors = out[1:]
self.assertTrue(custom_json.startswith('{'))
self.assertEqual(len(colors), 8)
self.assertTrue(all(isinstance(c, str) and c.startswith('#') for c in colors))
# Verify JSON parsable and has 8 colors
import json
data = json.loads(custom_json)
self.assertIn('colors', data)
self.assertEqual(len(data['colors']), 8)
self.assertTrue(all(isinstance(c, str) and c.startswith('#') for c in data['colors']))
if __name__ == '__main__':