mirror of
https://github.com/gradio-app/gradio.git
synced 2025-01-30 11:00:11 +08:00
restore cropper functionality
This commit is contained in:
parent
5ff983f7a2
commit
b1ee6f2ae5
@ -8,15 +8,13 @@ def image_mod(image):
|
||||
|
||||
|
||||
iface = gr.Interface(image_mod,
|
||||
gr.inputs.Image(type="pil", optional=True),
|
||||
gr.inputs.Image(type="pil", optional=True, tool="select"),
|
||||
"image",
|
||||
examples=[
|
||||
["images/cheetah1.jpg"],
|
||||
["images/cheetah2.jpg"],
|
||||
["images/lion.jpg"],
|
||||
])
|
||||
|
||||
iface.test_launch()
|
||||
], live=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
iface.launch()
|
||||
|
13
frontend/package-lock.json
generated
13
frontend/package-lock.json
generated
@ -5005,6 +5005,11 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"cropperjs": {
|
||||
"version": "1.5.12",
|
||||
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.12.tgz",
|
||||
"integrity": "sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw=="
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@ -15446,6 +15451,14 @@
|
||||
"lodash.curry": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"react-cropper": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/react-cropper/-/react-cropper-2.1.8.tgz",
|
||||
"integrity": "sha512-QEj6CE9et/gMRqpaKMgZQdBgtzLjjq+zj1pmHwtoWG6GqscDl4QpTwoEElWN2pieYxkwFaZa0lPiD2b9nwqLKQ==",
|
||||
"requires": {
|
||||
"cropperjs": "^1.5.12"
|
||||
}
|
||||
},
|
||||
"react-dev-utils": {
|
||||
"version": "11.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz",
|
||||
|
@ -20,6 +20,7 @@
|
||||
"prettier-eslint": "^13.0.0",
|
||||
"prettier-eslint-cli": "^5.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-cropper": "^2.1.8",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-json-tree": "^0.15.0",
|
||||
"react-plotly.js": "^2.5.1",
|
||||
|
@ -61,42 +61,169 @@ import {
|
||||
} from "./components/output/timeseries";
|
||||
import { VideoOutput, VideoOutputExample } from "./components/output/video";
|
||||
|
||||
let input_component_map = {
|
||||
audio: [AudioInput, AudioInputExample],
|
||||
checkboxgroup: [CheckboxGroupInput, CheckboxGroupInputExample],
|
||||
checkbox: [CheckboxInput, CheckboxInputExample],
|
||||
dataframe: [DataframeInput, DataframeInputExample],
|
||||
dropdown: [DropdownInput, DropdownInputExample],
|
||||
file: [FileInput, FileInputExample],
|
||||
image: [ImageInput, ImageInputExample],
|
||||
number: [NumberInput, NumberInputExample],
|
||||
radio: [RadioInput, RadioInputExample],
|
||||
slider: [SliderInput, SliderInputExample],
|
||||
textbox: [TextboxInput, TextboxInputExample],
|
||||
timeseries: [TimeseriesInput, TimeseriesInputExample],
|
||||
video: [VideoInput, VideoInputExample]
|
||||
};
|
||||
let output_component_map = {
|
||||
audio: [AudioOutput, AudioOutputExample],
|
||||
carousel: [CarouselOutput, CarouselOutputExample],
|
||||
dataframe: [DataframeOutput, DataframeOutputExample],
|
||||
file: [FileOutput, FileOutputExample],
|
||||
highlightedtext: [HighlightedTextOutput, HighlightedTextOutputExample],
|
||||
html: [HTMLOutput, HTMLOutputExample],
|
||||
image: [ImageOutput, ImageOutputExample],
|
||||
json: [JSONOutput, JSONOutputExample],
|
||||
keyvalues: [KeyValuesOutput, KeyValuesOutputExample],
|
||||
label: [LabelOutput, LabelOutputExample],
|
||||
textbox: [TextboxOutput, TextboxOutputExample],
|
||||
timeseries: [TimeseriesOutput, TimeseriesOutputExample],
|
||||
video: [VideoOutput, VideoOutputExample]
|
||||
};
|
||||
let input_component_set = [
|
||||
{
|
||||
name: "audio",
|
||||
component: AudioInput,
|
||||
memoized_component: null,
|
||||
example_component: AudioInputExample
|
||||
},
|
||||
{
|
||||
name: "checkboxgroup",
|
||||
component: CheckboxGroupInput,
|
||||
memoized_component: null,
|
||||
example_component: CheckboxGroupInputExample
|
||||
},
|
||||
{
|
||||
name: "checkbox",
|
||||
component: CheckboxInput,
|
||||
memoized_component: null,
|
||||
example_component: CheckboxInputExample
|
||||
},
|
||||
{
|
||||
name: "dataframe",
|
||||
component: DataframeInput,
|
||||
memoized_component: null,
|
||||
example_component: DataframeInputExample
|
||||
},
|
||||
{
|
||||
name: "dropdown",
|
||||
component: DropdownInput,
|
||||
memoized_component: null,
|
||||
example_component: DropdownInputExample
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
component: FileInput,
|
||||
memoized_component: null,
|
||||
example_component: FileInputExample
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
component: ImageInput,
|
||||
memoized_component: null,
|
||||
example_component: ImageInputExample
|
||||
},
|
||||
{
|
||||
name: "number",
|
||||
component: NumberInput,
|
||||
memoized_component: null,
|
||||
example_component: NumberInputExample
|
||||
},
|
||||
{
|
||||
name: "radio",
|
||||
component: RadioInput,
|
||||
memoized_component: null,
|
||||
example_component: RadioInputExample
|
||||
},
|
||||
{
|
||||
name: "slider",
|
||||
component: SliderInput,
|
||||
memoized_component: null,
|
||||
example_component: SliderInputExample
|
||||
},
|
||||
{
|
||||
name: "textbox",
|
||||
component: TextboxInput,
|
||||
memoized_component: null,
|
||||
example_component: TextboxInputExample
|
||||
},
|
||||
{
|
||||
name: "timeseries",
|
||||
component: TimeseriesInput,
|
||||
memoized_component: null,
|
||||
example_component: TimeseriesInputExample
|
||||
},
|
||||
{ name: "video", component: VideoInput, example_component: VideoInputExample }
|
||||
];
|
||||
let output_component_set = [
|
||||
{
|
||||
name: "audio",
|
||||
component: AudioOutput,
|
||||
memoized_component: null,
|
||||
example_component: AudioOutputExample
|
||||
},
|
||||
{
|
||||
name: "carousel",
|
||||
component: CarouselOutput,
|
||||
memoized_component: null,
|
||||
example_component: CarouselOutputExample
|
||||
},
|
||||
{
|
||||
name: "dataframe",
|
||||
component: DataframeOutput,
|
||||
memoized_component: null,
|
||||
example_component: DataframeOutputExample
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
component: FileOutput,
|
||||
memoized_component: null,
|
||||
example_component: FileOutputExample
|
||||
},
|
||||
{
|
||||
name: "highlightedtext",
|
||||
component: HighlightedTextOutput,
|
||||
memoized_component: null,
|
||||
example_component: HighlightedTextOutputExample
|
||||
},
|
||||
{
|
||||
name: "html",
|
||||
component: HTMLOutput,
|
||||
memoized_component: null,
|
||||
example_component: HTMLOutputExample
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
component: ImageOutput,
|
||||
memoized_component: null,
|
||||
example_component: ImageOutputExample
|
||||
},
|
||||
{
|
||||
name: "json",
|
||||
component: JSONOutput,
|
||||
memoized_component: null,
|
||||
example_component: JSONOutputExample
|
||||
},
|
||||
{
|
||||
name: "keyvalues",
|
||||
component: KeyValuesOutput,
|
||||
memoized_component: null,
|
||||
example_component: KeyValuesOutputExample
|
||||
},
|
||||
{
|
||||
name: "label",
|
||||
component: LabelOutput,
|
||||
memoized_component: null,
|
||||
example_component: LabelOutputExample
|
||||
},
|
||||
{
|
||||
name: "textbox",
|
||||
component: TextboxOutput,
|
||||
memoized_component: null,
|
||||
example_component: TextboxOutputExample
|
||||
},
|
||||
{
|
||||
name: "timeseries",
|
||||
component: TimeseriesOutput,
|
||||
memoized_component: null,
|
||||
example_component: TimeseriesOutputExample
|
||||
},
|
||||
{
|
||||
name: "video",
|
||||
component: VideoOutput,
|
||||
memoized_component: null,
|
||||
example_component: VideoOutputExample
|
||||
}
|
||||
];
|
||||
|
||||
for (let component_map of [input_component_map, output_component_map]) {
|
||||
for (let [key, components] of Object.entries(component_map)) {
|
||||
let component = components[0];
|
||||
component_map[key][0] = React.memo(component, component.memo);
|
||||
for (let component_set of [input_component_set, output_component_set]) {
|
||||
for (let component_data of component_set) {
|
||||
component_data.memoized_component = React.memo(
|
||||
component_data.component,
|
||||
component_data.component.memo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { input_component_map, output_component_map };
|
||||
export { input_component_set, output_component_set };
|
||||
|
@ -4,4 +4,7 @@ export default class BaseComponent extends React.Component {
|
||||
static memo = (a, b) => {
|
||||
return a.value === b.value && a.interpretation === b.interpretation;
|
||||
};
|
||||
static postprocess = (y) => {
|
||||
return y;
|
||||
};
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import { SketchField, Tools } from "../../vendor/ReactSketch";
|
||||
import { getObjectFitSize, paintSaliency } from "../../utils";
|
||||
import "tui-image-editor/dist/tui-image-editor.css";
|
||||
import ImageEditor from "@toast-ui/react-image-editor";
|
||||
import Cropper from "react-cropper";
|
||||
import "cropperjs/dist/cropper.css";
|
||||
|
||||
class ImageInput extends BaseComponent {
|
||||
constructor(props) {
|
||||
@ -26,9 +28,21 @@ class ImageInput extends BaseComponent {
|
||||
this.webcamRef = React.createRef();
|
||||
this.sketchRef = React.createRef();
|
||||
this.editorRef = React.createRef();
|
||||
this.cropperRef = React.createRef();
|
||||
this.sketchKey = 0;
|
||||
this.state = { editorMode: false };
|
||||
}
|
||||
static memo = (a, b) => {
|
||||
if (a.interpretation != b.interpretation) {
|
||||
return false;
|
||||
} else if (a.value === null && b.value === null) {
|
||||
return true;
|
||||
} else if (a.value === null || b.value === null) {
|
||||
return false;
|
||||
} else {
|
||||
return a.value.src === b.value.src;
|
||||
}
|
||||
};
|
||||
handleChange(data) {
|
||||
this.props.handleChange(data);
|
||||
}
|
||||
@ -37,18 +51,18 @@ class ImageInput extends BaseComponent {
|
||||
}
|
||||
snapshot() {
|
||||
let imageSrc = this.webcamRef.current.getScreenshot();
|
||||
this.handleChange(imageSrc);
|
||||
this.handleChange({ src: imageSrc, crop: null });
|
||||
}
|
||||
getSketch() {
|
||||
let imageSrc = this.sketchRef.current.toDataURL();
|
||||
this.handleChange(imageSrc);
|
||||
this.handleChange({ src: imageSrc, crop: null });
|
||||
}
|
||||
cancelEditor() {
|
||||
this.setState({ editorMode: false });
|
||||
}
|
||||
saveEditor() {
|
||||
const editorInstance = this.editorRef.current.getInstance();
|
||||
this.handleChange(editorInstance.toDataURL());
|
||||
this.handleChange({ src: editorInstance.toDataURL(), crop: null });
|
||||
this.setState({ editorMode: false });
|
||||
}
|
||||
onImgLoad({ target: img }) {
|
||||
@ -62,6 +76,10 @@ class ImageInput extends BaseComponent {
|
||||
openEditor() {
|
||||
this.setState({ editorMode: true });
|
||||
}
|
||||
onCrop = () => {
|
||||
const crop = this.cropperRef.current.cropper.getCroppedCanvas().toDataURL();
|
||||
this.handleChange({ src: this.props.value.src, crop: crop });
|
||||
};
|
||||
render() {
|
||||
let no_action = (evt) => {
|
||||
evt.preventDefault();
|
||||
@ -107,39 +125,53 @@ class ImageInput extends BaseComponent {
|
||||
return (
|
||||
<div className="input_image">
|
||||
<div className="image_preview_holder">
|
||||
{this.state.editorMode ? (
|
||||
<div className="image_editor">
|
||||
<div className="image_editor_buttons">
|
||||
<button onClick={this.saveEditor}>Save</button>
|
||||
<button onClick={this.cancelEditor}>Cancel</button>
|
||||
{this.props.tool === "editor" ? (
|
||||
this.state.editorMode ? (
|
||||
<div className="image_editor">
|
||||
<div className="image_editor_buttons">
|
||||
<button onClick={this.saveEditor}>Save</button>
|
||||
<button onClick={this.cancelEditor}>Cancel</button>
|
||||
</div>
|
||||
<ImageEditor
|
||||
ref={this.editorRef}
|
||||
includeUI={{
|
||||
loadImage: { path: this.props.value.src, name: "value" },
|
||||
uiSize: {
|
||||
width: "800px",
|
||||
height: "600px"
|
||||
},
|
||||
menuBarPosition: "left"
|
||||
}}
|
||||
cssMaxHeight={500}
|
||||
cssMaxWidth={700}
|
||||
usageStatistics={false}
|
||||
/>
|
||||
</div>
|
||||
<ImageEditor
|
||||
ref={this.editorRef}
|
||||
includeUI={{
|
||||
loadImage: { path: this.props.value, name: "value" },
|
||||
uiSize: {
|
||||
width: "800px",
|
||||
height: "600px"
|
||||
},
|
||||
menuBarPosition: "left"
|
||||
}}
|
||||
cssMaxHeight={500}
|
||||
cssMaxWidth={700}
|
||||
usageStatistics={false}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<button className="edit_button" onClick={this.openEditor}>
|
||||
Edit
|
||||
</button>
|
||||
)
|
||||
) : (
|
||||
<button className="edit_button" onClick={this.openEditor}>
|
||||
Edit
|
||||
</button>
|
||||
false
|
||||
)}
|
||||
{this.props.tool === "select" ? (
|
||||
<Cropper
|
||||
src={this.props.value.src}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
ref={this.cropperRef}
|
||||
crop={this.onCrop}
|
||||
autoCropArea={1}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
ref={this.imgRef}
|
||||
onLoad={this.onImgLoad}
|
||||
className="image_preview"
|
||||
alt=""
|
||||
src={this.props.value.src}
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
ref={this.imgRef}
|
||||
onLoad={this.onImgLoad}
|
||||
className="image_preview"
|
||||
alt=""
|
||||
src={this.props.value}
|
||||
/>
|
||||
</div>
|
||||
{interpretation}
|
||||
</div>
|
||||
@ -229,12 +261,19 @@ class ImageInput extends BaseComponent {
|
||||
var ReaderObj = new FileReader();
|
||||
ReaderObj.readAsDataURL(files[0]);
|
||||
ReaderObj.onloadend = function () {
|
||||
component.props.handleChange(this.result);
|
||||
component.props.handleChange({ src: this.result, crop: null });
|
||||
};
|
||||
}
|
||||
static postprocess = (y) => {
|
||||
return y.crop === null ? y.src : y.crop;
|
||||
};
|
||||
}
|
||||
|
||||
class ImageInputExample extends DataURLComponentExample {
|
||||
static async preprocess(x, examples_dir) {
|
||||
let src = await DataURLComponentExample.preprocess(x, examples_dir);
|
||||
return { src: src, crop: null };
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<img
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import BaseComponent from "../base_component";
|
||||
import ComponentExample from "../component_example";
|
||||
import { output_component_map } from "../../components";
|
||||
import { output_component_set } from "../../components";
|
||||
import arrowRight from "../../static/img/arrow-right.svg";
|
||||
import arrowLeft from "../../static/img/arrow-left.svg";
|
||||
|
||||
@ -36,7 +36,9 @@ class CarouselOutput extends BaseComponent {
|
||||
return (
|
||||
<div className="output_carousel">
|
||||
{this.props.components.map((config, index) => {
|
||||
let Component = output_component_map[config.name][0];
|
||||
let Component = output_component_set.find(
|
||||
(c) => c.name === config.name
|
||||
).memoized_component;
|
||||
return (
|
||||
<div className="component" key={index}>
|
||||
{config.label ? (
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import html2canvas from "html2canvas-objectfit-fix";
|
||||
import { input_component_map, output_component_map } from "./components";
|
||||
import { input_component_set, output_component_set } from "./components";
|
||||
import { saveAs } from "./utils";
|
||||
import ReactDOM from "react-dom";
|
||||
import classNames from "classnames";
|
||||
@ -104,14 +104,17 @@ export class GradioInterface extends React.Component {
|
||||
}
|
||||
this.pending_response = true;
|
||||
let input_state = [];
|
||||
for (let i = 0; i < this.props.input_components.length; i++) {
|
||||
for (let [i, input_component] of this.props.input_components.entries()) {
|
||||
if (
|
||||
this.state[i] === null &&
|
||||
this.props.input_components[i].optional !== true
|
||||
) {
|
||||
return;
|
||||
}
|
||||
input_state[i] = this.state[i];
|
||||
let InputComponentClass = input_component_set.find(
|
||||
(c) => c.name === input_component.name
|
||||
).component;
|
||||
input_state[i] = InputComponentClass.postprocess(this.state[i]);
|
||||
}
|
||||
this.setState({
|
||||
submitting: true,
|
||||
@ -237,14 +240,22 @@ export class GradioInterface extends React.Component {
|
||||
handleExampleChange = (example_id) => {
|
||||
this.setState({ example_id: example_id });
|
||||
for (let [i, item] of this.props.examples[example_id].entries()) {
|
||||
let ExampleComponent =
|
||||
i < this.props.input_components.length
|
||||
? input_component_map[this.props.input_components[i].name][1]
|
||||
: output_component_map[
|
||||
this.props.output_components[
|
||||
i - this.props.input_components.length
|
||||
].name
|
||||
][1];
|
||||
let ExampleComponent;
|
||||
if (i < this.props.input_components.length) {
|
||||
let component_name = this.props.input_components[i].name;
|
||||
let component_data = input_component_set.find(
|
||||
(c) => c.name === component_name
|
||||
);
|
||||
ExampleComponent = component_data.example_component;
|
||||
} else {
|
||||
let component_name =
|
||||
this.props.output_components[i - this.props.input_components.length]
|
||||
.name;
|
||||
let component_data = output_component_set.find(
|
||||
(c) => c.name === component_name
|
||||
);
|
||||
ExampleComponent = component_data.example_component;
|
||||
}
|
||||
ExampleComponent.preprocess(item, this.examples_dir).then((data) => {
|
||||
this.handleChange(i, data);
|
||||
});
|
||||
@ -290,7 +301,9 @@ export class GradioInterface extends React.Component {
|
||||
>
|
||||
<div className="component_set">
|
||||
{this.props.input_components.map((component, index) => {
|
||||
const Component = input_component_map[component.name][0];
|
||||
const Component = input_component_set.find(
|
||||
(c) => c.name === component.name
|
||||
).memoized_component;
|
||||
return (
|
||||
<div className="component" key={index}>
|
||||
<div className="panel_header">{component.label}</div>
|
||||
@ -335,7 +348,9 @@ export class GradioInterface extends React.Component {
|
||||
>
|
||||
{status}
|
||||
{this.props.output_components.map((component, index) => {
|
||||
const Component = output_component_map[component.name][0];
|
||||
const Component = output_component_set.find(
|
||||
(c) => c.name === component.name
|
||||
).memoized_component;
|
||||
const key = this.props.input_components.length + index;
|
||||
return this.state[key] === null ? (
|
||||
false
|
||||
@ -499,10 +514,9 @@ class GradioInterfaceExamples extends React.Component {
|
||||
onClick={() => this.props.handleExampleChange(i)}
|
||||
>
|
||||
{example_row.map((example_data, j) => {
|
||||
let ExampleComponent =
|
||||
input_component_map[
|
||||
this.props.input_components[j].name
|
||||
][1];
|
||||
let ExampleComponent = input_component_set.find(
|
||||
(c) => c.name === this.props.input_components[j].name
|
||||
).example_component;
|
||||
return (
|
||||
<td>
|
||||
<ExampleComponent
|
||||
|
Loading…
Reference in New Issue
Block a user