ImagePaletteExtractor: change outputs to JSON-only (custom_json); update tests
This commit is contained in:
@@ -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,)
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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__':
|
||||
|
||||
Reference in New Issue
Block a user