Color picker (#1695)

* Add new colorpicker component skeleton

* Updated colorpicker skeleton with some functionalities

* - Fix default input color

- Change input style

* Add colorpicker demo

* Add tests and code format

Co-authored-by: mfumanelli <fumanellimartina@gmail.com>
This commit is contained in:
Luigi Cerone 2022-07-05 07:50:17 +02:00 committed by GitHub
parent 68afe67497
commit 8f89270f50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 321 additions and 0 deletions

42
demo/color_picker/run.py Normal file
View File

@ -0,0 +1,42 @@
import gradio as gr
import numpy as np
from PIL import Image, ImageColor
def change_color(icon, color):
"""
Function that given an icon in .png format changes its color
Args:
icon: Icon whose color needs to be changed.
color: Chosen color with which to edit the input icon.
Returns:
edited_image: Edited icon.
"""
img = icon.convert("LA")
img = img.convert("RGBA")
image_np = np.array(icon)
_, _, _, alpha = image_np.T
mask = (alpha > 0)
image_np[..., :-1][mask.T] = ImageColor.getcolor(color, "RGB")
edited_image = Image.fromarray(image_np)
return edited_image
inputs = [
gr.Image(label="icon", type="pil", image_mode="RGBA"),
gr.ColorPicker(label="color")
]
outputs = gr.Image(label="colored icon")
demo = gr.Interface(
fn=change_color,
inputs=inputs,
outputs=outputs,
)
if __name__ == "__main__":
demo.launch()

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -16,6 +16,7 @@ from gradio.components import (
Checkbox,
Checkboxgroup,
CheckboxGroup,
ColorPicker,
DataFrame,
Dataframe,
Dataset,

View File

@ -2826,6 +2826,117 @@ class Button(Clickable, IOComponent):
)
class ColorPicker(Changeable, Submittable, IOComponent):
"""
Creates a color picker for user to select a color as string input.
Preprocessing: passes selected color value as a {str} into the function.
Postprocessing: expects a {str} returned from function and sets color picker value to it.
Demos: color_picker
"""
def __init__(
self,
value: str = None,
*,
label: Optional[str] = None,
show_label: bool = True,
interactive: Optional[bool] = None,
visible: bool = True,
elem_id: Optional[str] = None,
**kwargs,
):
"""
Parameters:
value (str): default text to provide in color picker.
label (Optional[str]): component name in interface.
show_label (bool): if True, will display label.
interactive (Optional[bool]): if True, will be rendered as an editable color picker; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
visible (bool): If False, component will be hidden.
elem_id (Optional[str]): An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
"""
self.value = self.postprocess(value)
self.cleared_value = "#000000"
self.test_input = value
IOComponent.__init__(
self,
label=label,
show_label=show_label,
interactive=interactive,
visible=visible,
elem_id=elem_id,
**kwargs,
)
def get_config(self):
return {
"value": self.value,
**IOComponent.get_config(self),
}
@staticmethod
def update(
value: Optional[Any] = None,
label: Optional[str] = None,
show_label: Optional[bool] = None,
visible: Optional[bool] = None,
interactive: Optional[bool] = None,
):
updated_config = {
"value": value,
"label": label,
"show_label": show_label,
"visible": visible,
"__type__": "update",
}
return IOComponent.add_interactive_to_config(updated_config, interactive)
# Input Functionalities
def preprocess(self, x: str | None) -> Any:
"""
Any preprocessing needed to be performed on function input.
Parameters:
x (str): text
Returns:
(str): text
"""
if x is None:
return None
else:
return str(x)
def preprocess_example(self, x: str | None) -> Any:
"""
Any preprocessing needed to be performed on an example before being passed to the main function.
"""
if x is None:
return None
else:
return str(x)
def generate_sample(self) -> str:
return "#000000"
# Output Functionalities
def postprocess(self, y: str | None):
"""
Any postprocessing needed to be performed on function output.
Parameters:
y (str | None): text
Returns:
(str | None): text
"""
if y is None:
return None
else:
return str(y)
def deserialize(self, x):
"""
Convert from serialized output (e.g. base64 representation) from a call() to the interface to a human-readable version of the output (path of an image, etc.)
"""
return x
############################
# Only Output Components
############################

View File

@ -1669,5 +1669,57 @@ class TestModel3D(unittest.TestCase):
self.assertEqual(input_data.split(";")[1], output_data.split(";")[1])
class TestColorPicker(unittest.TestCase):
def test_component_functions(self):
"""
Preprocess, postprocess, serialize, save_flagged, restore_flagged, tokenize, generate_sample, get_config
"""
color_picker_input = gr.ColorPicker()
self.assertEqual(color_picker_input.preprocess("#000000"), "#000000")
self.assertEqual(color_picker_input.preprocess_example("#000000"), "#000000")
self.assertEqual(color_picker_input.postprocess(None), None)
self.assertEqual(color_picker_input.postprocess("#FFFFFF"), "#FFFFFF")
self.assertEqual(color_picker_input.serialize("#000000", True), "#000000")
color_picker_input.interpretation_replacement = "unknown"
self.assertEqual(
color_picker_input.get_config(),
{
"value": None,
"show_label": True,
"label": None,
"style": {},
"elem_id": None,
"visible": True,
"interactive": None,
"name": "colorpicker",
},
)
self.assertIsInstance(color_picker_input.generate_sample(), str)
def test_in_interface_as_input(self):
"""
Interface, process, interpret,
"""
iface = gr.Interface(lambda x: x, "colorpicker", "colorpicker")
self.assertEqual(iface.process(["#000000"]), ["#000000"])
def test_in_interface_as_output(self):
"""
Interface, process
"""
iface = gr.Interface(lambda x: x, "colorpicker", gr.ColorPicker())
self.assertEqual(iface.process(["#000000"]), ["#000000"])
def test_static(self):
"""
postprocess
"""
component = gr.ColorPicker("#000000")
self.assertEqual(component.get_config().get("value"), "#000000")
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,40 @@
<svelte:options accessors={true} />
<script lang="ts">
import { ColorPicker } from "@gradio/form";
import { Block } from "@gradio/atoms";
import StatusTracker from "../StatusTracker/StatusTracker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
export let label: string = "ColorPicker";
export let elem_id: string = "";
export let visible: boolean = true;
export let value: string;
export let form_position: "first" | "last" | "mid" | "single" = "single";
export let show_label: boolean;
export let style: Record<string, unknown> = {};
export let loading_status: LoadingStatus;
export let mode: "static" | "dynamic";
</script>
<Block
{visible}
{form_position}
{elem_id}
disable={typeof style.container === "boolean" && !style.container}
>
<StatusTracker {...loading_status} />
<ColorPicker
{style}
bind:value
{label}
{show_label}
on:change
on:submit
disabled={mode === "static"}
/>
</Block>

View File

@ -0,0 +1,35 @@
import { test, describe, assert, afterEach } from "vitest";
import { cleanup, render } from "@gradio/tootils";
import ColorPicker from "./ColorPicker.svelte";
describe("ColorPicker", () => {
afterEach(() => cleanup());
test("renders provided value", () => {
const { getByDisplayValue } = render(ColorPicker, {
value: "#000000",
label: "ColorPicker"
});
const item: HTMLInputElement = getByDisplayValue("#000000");
assert.equal(item.value, "#000000");
});
test("changing the color should update the value", async () => {
const { component, getByDisplayValue } = render(ColorPicker, {
value: "#000000",
label: "ColorPicker"
});
const item: HTMLInputElement = getByDisplayValue("#000000");
assert.equal(item.value, "#000000");
await component.$set({
value: "#FFFFFF"
});
assert.equal(component.value, "#FFFFFF");
});
});

View File

@ -0,0 +1,2 @@
export { default as Component } from "./ColorPicker.svelte";
export const modes = ["static", "dynamic"];

View File

@ -6,6 +6,7 @@ export const component_map: Record<string, any> = {
chatbot: () => import("./Chatbot"),
checkbox: () => import("./Checkbox"),
checkboxgroup: () => import("./CheckboxGroup"),
colorpicker: () => import("./ColorPicker"),
column: () => import("./Column"),
dataframe: () => import("./DataFrame"),
dataset: () => import("./Dataset"),

View File

@ -0,0 +1,36 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { get_styles } from "@gradio/utils";
import { BlockTitle } from "@gradio/atoms";
export let value: string = "#000000";
export let style: Record<string, unknown> = {};
export let label: string;
export let disabled = false;
export let show_label: boolean = true;
$: value;
$: handle_change(value);
const dispatch = createEventDispatcher<{
change: string;
submit: undefined;
}>();
function handle_change(val: string) {
dispatch("change", val);
}
$: ({ classes } = get_styles(style, ["rounded", "border"]));
</script>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block">
<BlockTitle {show_label}>{label}</BlockTitle>
<input
type="color"
class="gr-box-unrounded {classes}"
bind:value
{disabled}
/>
</label>

View File

@ -5,3 +5,4 @@ export { default as Number } from "./Number.svelte";
export { default as Radio } from "./Radio.svelte";
export { default as TextBox } from "./Textbox.svelte";
export { default as Range } from "./Range.svelte";
export { default as ColorPicker } from "./ColorPicker.svelte";