Skip to content
Snippets Groups Projects
Commit a160f11d authored by imranasghar96@hotmail.com's avatar imranasghar96@hotmail.com
Browse files

Add 'constrain image proportions' functionality ...

... in the image annotation input/edit

- If the constrain proportions checkbox is checked in image annotation input, this functionality will make sure that the image proportions are maintained if either a new width or height is entered
- These image annotation insert/edit changes attempt to implement image insert/edit workflow similar to Mirador 2
parent ac164d8b
No related branches found
No related tags found
No related merge requests found
...@@ -17,17 +17,20 @@ import StrokeColorIcon from '@material-ui/icons/BorderColor'; ...@@ -17,17 +17,20 @@ import StrokeColorIcon from '@material-ui/icons/BorderColor';
import LineWeightIcon from '@material-ui/icons/LineWeight'; import LineWeightIcon from '@material-ui/icons/LineWeight';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import FormatShapesIcon from '@material-ui/icons/FormatShapes'; import FormatShapesIcon from '@material-ui/icons/FormatShapes';
import InsertPhotoIcon from '@material-ui/icons/InsertPhoto';
import Popover from '@material-ui/core/Popover'; import Popover from '@material-ui/core/Popover';
import Divider from '@material-ui/core/Divider'; import Divider from '@material-ui/core/Divider';
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from '@material-ui/core/MenuItem';
import MenuList from '@material-ui/core/MenuList';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle'; import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent'; import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText'; import DialogContentText from '@material-ui/core/DialogContentText';
import DialogActions from '@material-ui/core/DialogActions'; import DialogActions from '@material-ui/core/DialogActions';
import TextField from '@material-ui/core/TextField'; import TextField from '@material-ui/core/TextField';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from "@material-ui/core/FormControlLabel";
import ClickAwayListener from '@material-ui/core/ClickAwayListener'; import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import MenuList from '@material-ui/core/MenuList';
import { SketchPicker } from 'react-color'; import { SketchPicker } from 'react-color';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
...@@ -36,17 +39,15 @@ import AnnotationDrawing from './AnnotationDrawing'; ...@@ -36,17 +39,15 @@ import AnnotationDrawing from './AnnotationDrawing';
import TextEditor from './TextEditor'; import TextEditor from './TextEditor';
import WebAnnotation from './WebAnnotation'; import WebAnnotation from './WebAnnotation';
import CursorIcon from './icons/Cursor'; import CursorIcon from './icons/Cursor';
import InsertPhotoIcon from '@material-ui/icons/InsertPhoto';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from "@material-ui/core/FormControlLabel";
//
/** */ /** */
class AnnotationCreation extends Component { class AnnotationCreation extends Component {
/** */ /** */
constructor(props) { constructor(props) {
super(props); super(props);
const annoState = {}; const annoState = {};
annoState.image = false annoState.image = false;
if (props.annotation) { if (props.annotation) {
if (Array.isArray(props.annotation.body)) { if (Array.isArray(props.annotation.body)) {
annoState.tags = []; annoState.tags = [];
...@@ -55,17 +56,18 @@ class AnnotationCreation extends Component { ...@@ -55,17 +56,18 @@ class AnnotationCreation extends Component {
annoState.tags.push(body.value); annoState.tags.push(body.value);
} else if (body.type === 'TextualBody') { } else if (body.type === 'TextualBody') {
annoState.textBody = body.value; annoState.textBody = body.value;
} else if (body.type === 'ImageBody') { } else if (body.type === 'imgBody') {
annoState.textBody = body.value; annoState.textBody = body.value;
annoState.image = body.image; annoState.image = body.image;
} }
}); });
} else if (props.annotation.body.type === 'TextualBody') { } else if (props.annotation.body.type === 'TextualBody') {
annoState.textBody = props.annotation.body.value; annoState.textBody = props.annotation.body.value;
} else if (props.annotation.body.type === 'ImageBody') { } else if (props.annotation.body.type === 'imgBody') {
annoState.textBody = props.annotation.body.value; annoState.textBody = props.annotation.body.value;
annoState.image = props.annotation.body.image; annoState.image = props.annotation.body.image;
} }
if (Array.isArray(props.annotation.target.selector)) { if (Array.isArray(props.annotation.target.selector)) {
props.annotation.target.selector.forEach((selector) => { props.annotation.target.selector.forEach((selector) => {
if (selector.type === 'SvgSelector') { if (selector.type === 'SvgSelector') {
...@@ -93,14 +95,16 @@ class AnnotationCreation extends Component { ...@@ -93,14 +95,16 @@ class AnnotationCreation extends Component {
strokeWidth: 1, strokeWidth: 1,
svg: null, svg: null,
xywh: null, xywh: null,
imgConstraint: false, imgConstrain: false,
imgWidth: { imgWidth: {
value: '', value: '',
srcValue: '',
lastSubmittedValue: '', lastSubmittedValue: '',
validity: 0, validity: 0,
}, },
imgHeight: { imgHeight: {
value: '', value: '',
srcValue: '',
lastSubmittedValue: '', lastSubmittedValue: '',
validity: 0, validity: 0,
}, },
...@@ -109,7 +113,7 @@ class AnnotationCreation extends Component { ...@@ -109,7 +113,7 @@ class AnnotationCreation extends Component {
lastSubmittedValue: '', lastSubmittedValue: '',
validity: 0, validity: 0,
}, },
openAddImageDialog: false, openAddImgDialog: false,
...annoState, ...annoState,
}; };
...@@ -128,17 +132,18 @@ class AnnotationCreation extends Component { ...@@ -128,17 +132,18 @@ class AnnotationCreation extends Component {
this.handleCloseLineWeight = this.handleCloseLineWeight.bind(this); this.handleCloseLineWeight = this.handleCloseLineWeight.bind(this);
this.closeChooseColor = this.closeChooseColor.bind(this); this.closeChooseColor = this.closeChooseColor.bind(this);
this.updateStrokeColor = this.updateStrokeColor.bind(this); this.updateStrokeColor = this.updateStrokeColor.bind(this);
this.handleConstraintCheck = this.handleConstraintCheck.bind(this); this.isConstrained = this.isConstrained.bind(this);
this.handleImageDialogChange = this.handleImageDialogChange.bind(this); this.handleConstrainCheck = this.handleConstrainCheck.bind(this);
this.handleImageDialogSubmit = this.handleImageDialogSubmit.bind(this); this.handleImgDialogChange = this.handleImgDialogChange.bind(this);
this.handleImgDialogSubmit = this.handleImgDialogSubmit.bind(this);
} }
/** */ /** */
handleImageDialogChange(open) { handleImgDialogChange(open) {
const { imgHeight, imgWidth, imgUrl } = this.state; const { imgHeight, imgWidth, imgUrl } = this.state;
this.setState({ this.setState({
openAddImageDialog: open, openAddImgDialog: open,
imgUrl: { imgUrl: {
...imgUrl, ...imgUrl,
validity: 1, validity: 1,
...@@ -157,14 +162,47 @@ class AnnotationCreation extends Component { ...@@ -157,14 +162,47 @@ class AnnotationCreation extends Component {
}); });
}; };
handleConstraintCheck() { //adjust the other dimension in proportion to the inputted dimension if constrained checkbox is checked
const value = this.state.imgConstraint; isConstrained(event) {
const { imgConstrain, imgWidth, imgHeight } = this.state;
const ratio = imgWidth.srcValue/imgHeight.srcValue;
if(imgConstrain){
if(event.target.id == 'width' && imgWidth.srcValue !== ''){
//set height to be the same as width if width is less than 0
const height = imgWidth.value > 0 ? imgWidth.value * (1/ratio) : imgWidth.value;
this.setState({
imgHeight: {
...imgHeight,
validity: 1,
value: height,
},
});
} else if(event.target.id == 'height' && imgHeight.srcValue !== ''){
//set width to be the same as height if height is less than 0
const width = imgHeight.value > 0 ? imgHeight.value * ratio : imgHeight.value;
this.setState({ this.setState({
imgConstraint: !value, imgWidth: {
...imgWidth,
validity: 1,
value: width,
},
}); });
} }
}
}
handleImageDialogSubmit() { handleConstrainCheck(event) {
const value = event.target.checked;
this.setState({
imgConstrain: value,
});
}
handleImgDialogSubmit() {
let open = true; let open = true;
const { imgUrl, imgHeight, imgWidth } = this.state; const { imgUrl, imgHeight, imgWidth } = this.state;
...@@ -176,27 +214,27 @@ class AnnotationCreation extends Component { ...@@ -176,27 +214,27 @@ class AnnotationCreation extends Component {
if (urlValidity == 1 && widthValidity == 1 && heightValidity == 1) { if (urlValidity == 1 && widthValidity == 1 && heightValidity == 1) {
open = false; open = false;
} }
console.log("U imgUrl.value",imgUrl.value)
console.log("U imgHeight.value",imgHeight.value)
console.log("U imgWidth.value",imgWidth.value)
this.setState({ this.setState({
imgUrl: { imgUrl: {
...imgUrl,
value: imgUrl.value, value: imgUrl.value,
validity: urlValidity, validity: urlValidity,
lastSubmittedValue: urlValidity == 1 ? imgUrl.value : imgUrl.lastSubmittedValue, lastSubmittedValue: urlValidity == 1 ? imgUrl.value : imgUrl.lastSubmittedValue,
}, },
imgHeight: { imgHeight: {
...imgHeight,
value: imgHeight.value, value: imgHeight.value,
validity: heightValidity, validity: heightValidity,
lastSubmittedValue: heightValidity == 1 ? imgHeight.value : imgHeight.lastSubmittedValue, lastSubmittedValue: heightValidity == 1 ? imgHeight.value : imgHeight.lastSubmittedValue,
}, },
imgWidth: { imgWidth: {
...imgWidth,
value: imgWidth.value, value: imgWidth.value,
validity: widthValidity, validity: widthValidity,
lastSubmittedValue: widthValidity == 1 ? imgWidth.value : imgWidth.lastSubmittedValue, lastSubmittedValue: widthValidity == 1 ? imgWidth.value : imgWidth.lastSubmittedValue,
}, },
openAddImageDialog: open, openAddImgDialog: open,
}); });
} }
...@@ -235,8 +273,8 @@ class AnnotationCreation extends Component { ...@@ -235,8 +273,8 @@ class AnnotationCreation extends Component {
const img = new Image(); const img = new Image();
img.src = url; img.src = url;
img.onload = () => resolve({height: img.height, width: img.width}) img.onload = () => resolve({height: img.height, width: img.width});
img.onerror = reject img.onerror = reject;
}) })
} }
...@@ -252,15 +290,17 @@ class AnnotationCreation extends Component { ...@@ -252,15 +290,17 @@ class AnnotationCreation extends Component {
}, },
imgHeight: { imgHeight: {
...imgHeight, ...imgHeight,
srcValue: dimensions.height || '',
value: dimensions.height || '', value: dimensions.height || '',
}, },
imgWidth: { imgWidth: {
...imgWidth, ...imgWidth,
srcValue: dimensions.width || '',
value: dimensions.width || '', value: dimensions.width || '',
}, },
}); });
} catch (e) { } catch (e) {
console.error('Error!'); console.error('Error! Dimensions could not be retrieved from image.');
} }
} }
...@@ -318,23 +358,23 @@ class AnnotationCreation extends Component { ...@@ -318,23 +358,23 @@ class AnnotationCreation extends Component {
submitForm(e) { submitForm(e) {
e.preventDefault(); e.preventDefault();
const { const {
annotation, canvases, closeCompanionWindow, receiveAnnotation, config, imgConstraint annotation, canvases, closeCompanionWindow, receiveAnnotation, config, imgConstrain
} = this.props; } = this.props;
const { const {
textBody, image, imgWidth, imgHeight, imgUrl, tags, xywh, svg, textBody, image, imgWidth, imgHeight, imgUrl, tags, xywh, svg,
} = this.state; } = this.state;
let annoBody = {value: textBody} let annoBody = {value: textBody}
let imageBody let imgBody;
if(imgWidth.validity == 1 && imgHeight.validity == 1 && imgUrl.validity == 1){ if(imgWidth.validity == 1 && imgHeight.validity == 1 && imgUrl.validity == 1){
imageBody = { imgBody = {
w: imgWidth.value, w: imgWidth.value,
h: imgHeight.value, h: imgHeight.value,
url: imgUrl.value, url: imgUrl.value,
constraint: imgConstraint, constrain: imgConstrain,
} }
} else { } else {
imageBody = image imgBody = image;
} }
canvases.forEach((canvas) => { canvases.forEach((canvas) => {
...@@ -347,7 +387,7 @@ class AnnotationCreation extends Component { ...@@ -347,7 +387,7 @@ class AnnotationCreation extends Component {
manifestId: canvas.options.resource.id, manifestId: canvas.options.resource.id,
svg, svg,
tags, tags,
image: imageBody, image: imgBody,
xywh, xywh,
}).toJson(); }).toJson();
...@@ -369,7 +409,6 @@ class AnnotationCreation extends Component { ...@@ -369,7 +409,6 @@ class AnnotationCreation extends Component {
/** */ /** */
changeTool(e, tool) { changeTool(e, tool) {
console.log("tool", tool)
this.setState({ this.setState({
activeTool: tool, activeTool: tool,
}); });
...@@ -401,8 +440,8 @@ class AnnotationCreation extends Component { ...@@ -401,8 +440,8 @@ class AnnotationCreation extends Component {
} = this.props; } = this.props;
const { const {
activeTool, colorPopoverOpen, currentColorType, fillColor, openAddImageDialog, popoverAnchorEl, strokeColor, activeTool, colorPopoverOpen, currentColorType, fillColor, openAddImgDialog, popoverAnchorEl, strokeColor,
popoverLineWeightAnchorEl, lineWeightPopoverOpen, strokeWidth, closedMode, textBody, image, imgUrl, imgWidth, imgHeight, imgConstraint, svg, popoverLineWeightAnchorEl, lineWeightPopoverOpen, strokeWidth, closedMode, textBody, image, imgUrl, imgWidth, imgHeight, imgConstrain, svg,
} = this.state; } = this.state;
return ( return (
<CompanionWindow <CompanionWindow
...@@ -535,11 +574,11 @@ class AnnotationCreation extends Component { ...@@ -535,11 +574,11 @@ class AnnotationCreation extends Component {
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={12} style={{marginBottom: 10}}> <Grid item xs={12} style={{marginBottom: 10}}>
<ToggleButton value="image-icon" aria-label="insert an image" onClick={() => this.handleImageDialogChange(true)}> <ToggleButton value="image-icon" aria-label="insert an image" onClick={() => this.handleImgDialogChange(true)}>
<InsertPhotoIcon /> <InsertPhotoIcon />
</ToggleButton> </ToggleButton>
</Grid> </Grid>
<Dialog open={openAddImageDialog} fullWidth minWidth="20%" onClose={() => this.handleImageDialogChange(false)} aria-labelledby="form-dialog-title"> <Dialog open={openAddImgDialog} fullWidth minWidth="20%" onClose={() => this.handleImgDialogChange(false)} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Insert/Edit Image</DialogTitle> <DialogTitle id="form-dialog-title">Insert/Edit Image</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText> <DialogContentText>
...@@ -550,7 +589,7 @@ class AnnotationCreation extends Component { ...@@ -550,7 +589,7 @@ class AnnotationCreation extends Component {
onChange={e => this.setImgUrl(e.target.value)} onChange={e => this.setImgUrl(e.target.value)}
onBlur={e => this.getImgDimensions(e.target.value)} onBlur={e => this.getImgDimensions(e.target.value)}
error={imgUrl.validity == 2} error={imgUrl.validity == 2}
helperText={imgUrl.validity == 2 ? "Invalid Url" : ""} helperText={imgUrl.validity == 2 ? "Url Invalid" : ""}
margin="dense" margin="dense"
id="source" id="source"
label="Image Url" label="Image Url"
...@@ -566,6 +605,7 @@ class AnnotationCreation extends Component { ...@@ -566,6 +605,7 @@ class AnnotationCreation extends Component {
value={imgWidth.value} value={imgWidth.value}
style = {{width: 100, marginRight: 10}} style = {{width: 100, marginRight: 10}}
onChange={e => this.setImgWidth(e.target.value)} onChange={e => this.setImgWidth(e.target.value)}
onBlur={(e) => this.isConstrained(e)}
error={imgWidth.validity == 2} error={imgWidth.validity == 2}
helperText={imgWidth.validity == 2 ? "Invalid Width" : ""} helperText={imgWidth.validity == 2 ? "Invalid Width" : ""}
margin="dense" margin="dense"
...@@ -573,38 +613,37 @@ class AnnotationCreation extends Component { ...@@ -573,38 +613,37 @@ class AnnotationCreation extends Component {
label="Width" label="Width"
type="number" type="number"
variant="outlined" variant="outlined"
autoFocus
/> />
<TextField <TextField
value={imgHeight.value} value={imgHeight.value}
style = {{width: 100, marginLeft: 10 }} style = {{width: 100, marginLeft: 10 }}
onChange={e => this.setImgHeight(e.target.value)} onChange={e => this.setImgHeight(e.target.value)}
onBlur={(e) => this.isConstrained(e)}
error={imgHeight.validity == 2} error={imgHeight.validity == 2}
helperText={imgHeight.validity == 2 ? "Invalid Height" : ""} helperText={imgHeight.validity == 2 ? "Negative or invalid height." : ""}
margin="dense" margin="dense"
id="height" id="height"
label="Height" label="Height"
type="number" type="number"
variant="outlined" variant="outlined"
autoFocus
/> />
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
checked={imgConstraint} checked={imgConstrain}
onChange={this.handleConstraintCheck} onChange={(e) => this.handleConstrainCheck(e)}
inputProps={{ 'aria-label': 'primary checkbox' }} inputProps={{ 'aria-label': 'primary checkbox' }}
style = {{ marginLeft: 10 }} style = {{ marginLeft: 10 }}
/> />
} }
label="Constraint" label="Constrain Proportions"
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => this.handleImageDialogChange(false)} color="primary"> <Button onClick={() => this.handleImgDialogChange(false)} color="primary">
Cancel Cancel
</Button> </Button>
<Button onClick={this.handleImageDialogSubmit} color="primary"> <Button onClick={this.handleImgDialogSubmit} color="primary">
Add Add
</Button> </Button>
</DialogActions> </DialogActions>
......
...@@ -2,7 +2,14 @@ ...@@ -2,7 +2,14 @@
export default class WebAnnotation { export default class WebAnnotation {
/** */ /** */
constructor({ constructor({
canvasId, id, image, xywh, body, tags, svg, manifestId, canvasId,
id,
image,
xywh,
body,
tags,
svg,
manifestId,
}) { }) {
this.id = id; this.id = id;
this.canvasId = canvasId; this.canvasId = canvasId;
...@@ -36,11 +43,12 @@ export default class WebAnnotation { ...@@ -36,11 +43,12 @@ export default class WebAnnotation {
} }
if (this.image) { if (this.image) {
annoBody.type = 'ImageBody' annoBody.type = 'ImageBody';
Object.assign(annoBody, this.image); Object.assign(annoBody, this.image);
} }
bodies.push(annoBody); bodies.push(annoBody);
} }
if (this.tags) { if (this.tags) {
bodies = bodies.concat(this.tags.map((tag) => ({ bodies = bodies.concat(this.tags.map((tag) => ({
purpose: 'tagging', purpose: 'tagging',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment