Skip to content

Commit

Permalink
Calculate pixel ratio from the image's natural size. (mozilla-service…
Browse files Browse the repository at this point in the history
…s#4453, mozilla-services#4509)

- Ensure drawing canvas always match base canvas' dimensions.
- Scale a component's drawing context only when the canvas has been replaced.
  • Loading branch information
chenba committed Jun 1, 2018
1 parent 94f4c06 commit 24e723b
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 125 deletions.
74 changes: 43 additions & 31 deletions server/src/pages/shot/crop-tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ exports.CropTool = class CropTool extends React.Component {
selectionState: SelectionState.NONE,
cropSelection: null
};
this.canvasWidth = parseInt(props.baseCanvas.style.width, 10);
this.canvasHeight = parseInt(props.baseCanvas.style.height, 10);
this.canvasCssWidth = props.canvasCssWidth;
this.canvasCssHeight = props.canvasCssHeight;
}

componentDidMount() {
Expand Down Expand Up @@ -64,7 +64,7 @@ exports.CropTool = class CropTool extends React.Component {
const selectionBottomPx = `${this.state.cropSelection.bottom}px`;
const selectionHeightPx = `${this.state.cropSelection.height}px`;
const selectionWidthPx = `${this.state.cropSelection.width}px`;
const remainingRightSideWidthPx = `${this.canvasWidth - this.state.cropSelection.right}px`;
const remainingRightSideWidthPx = `${this.canvasCssWidth - this.state.cropSelection.right}px`;
const oneHundredPercent = "100%";

const bgTopStyles = {
Expand Down Expand Up @@ -143,8 +143,8 @@ exports.CropTool = class CropTool extends React.Component {
onClickConfirm(e) {
if (!this.state.cropSelection
|| !this.state.cropSelection.width || !this.state.cropSelection.height
|| (this.canvasWidth === this.state.cropSelection.width
&& this.canvasHeight === this.state.cropSelection.height)) {
|| (this.canvasCssWidth === this.state.cropSelection.width
&& this.canvasCssHeight === this.state.cropSelection.height)) {
if (this.props.confirmCropHandler) {
this.props.confirmCropHandler(null, null);
}
Expand All @@ -154,13 +154,13 @@ exports.CropTool = class CropTool extends React.Component {
}

const croppedImage = document.createElement("canvas");
croppedImage.width = this.state.cropSelection.width * this.props.devicePixelRatio;
croppedImage.height = this.state.cropSelection.height * this.props.devicePixelRatio;
croppedImage.width = this.state.cropSelection.width * this.props.canvasPixelRatio;
croppedImage.height = this.state.cropSelection.height * this.props.canvasPixelRatio;
const croppedContext = croppedImage.getContext("2d");
croppedContext.drawImage(
this.props.baseCanvas,
this.state.cropSelection.left * this.props.devicePixelRatio,
this.state.cropSelection.top * this.props.devicePixelRatio,
this.state.cropSelection.left * this.props.canvasPixelRatio,
this.state.cropSelection.top * this.props.canvasPixelRatio,
croppedImage.width, croppedImage.height,
0, 0, croppedImage.width, croppedImage.height);

Expand Down Expand Up @@ -227,12 +227,12 @@ exports.CropTool = class CropTool extends React.Component {

getDraggedSelection(e) {
const currentMousePosition = this.captureMousePosition(e);
return new Selection(
clamp(mousedownPosition.x, 0, this.canvasWidth),
clamp(mousedownPosition.y, 0, this.canvasHeight),
clamp(currentMousePosition.x, 0, this.canvasWidth),
clamp(currentMousePosition.y, 0, this.canvasHeight)
);
return floorSelection(new Selection(
clamp(mousedownPosition.x, 0, this.canvasCssWidth),
clamp(mousedownPosition.y, 0, this.canvasCssHeight),
clamp(currentMousePosition.x, 0, this.canvasCssWidth),
clamp(currentMousePosition.y, 0, this.canvasCssHeight)
));
}

onMouseUp(e) {
Expand Down Expand Up @@ -266,39 +266,39 @@ exports.CropTool = class CropTool extends React.Component {
updatedSelection = mousedownSelection.clone();
switch (dragHandleLocation) {
case "topLeft":
updatedSelection.top = clamp(mousedownSelection.top + yDelta, 0, this.canvasHeight);
updatedSelection.left = clamp(mousedownSelection.left + xDelta, 0, this.canvasWidth);
updatedSelection.top = clamp(mousedownSelection.top + yDelta, 0, this.canvasCssHeight);
updatedSelection.left = clamp(mousedownSelection.left + xDelta, 0, this.canvasCssWidth);
break;
case "top":
updatedSelection.top = clamp(mousedownSelection.top + yDelta, 0, this.canvasHeight);
updatedSelection.top = clamp(mousedownSelection.top + yDelta, 0, this.canvasCssHeight);
break;
case "topRight":
updatedSelection.right = clamp(mousedownSelection.right + xDelta, 0, this.canvasWidth);
updatedSelection.top = clamp(mousedownSelection.top + yDelta, 0, this.canvasHeight);
updatedSelection.right = clamp(mousedownSelection.right + xDelta, 0, this.canvasCssWidth);
updatedSelection.top = clamp(mousedownSelection.top + yDelta, 0, this.canvasCssHeight);
break;
case "left":
updatedSelection.left = clamp(mousedownSelection.left + xDelta, 0, this.canvasWidth);
updatedSelection.left = clamp(mousedownSelection.left + xDelta, 0, this.canvasCssWidth);
break;
case "right":
updatedSelection.right = clamp(mousedownSelection.right + xDelta, 0, this.canvasWidth);
updatedSelection.right = clamp(mousedownSelection.right + xDelta, 0, this.canvasCssWidth);
break;
case "bottomLeft":
updatedSelection.left = clamp(mousedownSelection.left + xDelta, 0, this.canvasWidth);
updatedSelection.bottom = clamp(mousedownSelection.bottom + yDelta, 0, this.canvasHeight);
updatedSelection.left = clamp(mousedownSelection.left + xDelta, 0, this.canvasCssWidth);
updatedSelection.bottom = clamp(mousedownSelection.bottom + yDelta, 0, this.canvasCssHeight);
break;
case "bottom":
updatedSelection.bottom = clamp(mousedownSelection.bottom + yDelta, 0, this.canvasHeight);
updatedSelection.bottom = clamp(mousedownSelection.bottom + yDelta, 0, this.canvasCssHeight);
break;
case "bottomRight":
updatedSelection.right = clamp(mousedownSelection.right + xDelta, 0, this.canvasWidth);
updatedSelection.bottom = clamp(mousedownSelection.bottom + yDelta, 0, this.canvasHeight);
updatedSelection.right = clamp(mousedownSelection.right + xDelta, 0, this.canvasCssWidth);
updatedSelection.bottom = clamp(mousedownSelection.bottom + yDelta, 0, this.canvasCssHeight);
break;
}
}

if (this.state.selectionState === SelectionState.MOVING) {
const maxLeft = this.canvasWidth - mousedownSelection.width;
const maxTop = this.canvasHeight - mousedownSelection.height;
const maxLeft = this.canvasCssWidth - mousedownSelection.width;
const maxTop = this.canvasCssHeight - mousedownSelection.height;
const newLeft = clamp(mousedownSelection.left + xDelta, 0, maxLeft);
const newTop = clamp(mousedownSelection.top + yDelta, 0, maxTop);

Expand All @@ -310,7 +310,7 @@ exports.CropTool = class CropTool extends React.Component {
);
}

this.setState({cropSelection: updatedSelection});
this.setState({cropSelection: floorSelection(updatedSelection)});
this.scrollIfByEdge(e);
return true;
}
Expand Down Expand Up @@ -338,9 +338,21 @@ exports.CropTool.propTypes = {
confirmCropHandler: PropTypes.func,
cancelCropHandler: PropTypes.func,
baseCanvas: PropTypes.object,
devicePixelRatio: PropTypes.number,
canvasPixelRatio: PropTypes.number,
canvasCssWidth: PropTypes.number,
canvasCssHeight: PropTypes.number
};

function clamp(val, min, max) {
return Math.min(Math.max(val, min), max);
}

// Decimals make for blurry images. This is a simple function to ensure whole
// numbers in a selection. It mutates and returns.
function floorSelection(selection) {
selection.left = Math.floor(selection.left);
selection.top = Math.floor(selection.top);
selection.right = Math.floor(selection.right);
selection.bottom = Math.floor(selection.bottom);
return selection;
}
35 changes: 23 additions & 12 deletions server/src/pages/shot/drawing-tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,45 @@ exports.DrawingTool = class DrawingTool extends React.Component {
constructor(props) {
super(props);
this.canvas = React.createRef();
this.canvasWidth = parseInt(props.baseCanvas.style.width, 10);
this.canvasHeight = parseInt(props.baseCanvas.style.height, 10);
}

render() {
return <canvas
ref={this.canvas}
className={`image-holder centered ${this.state.classNames}`}
onMouseDown={this.onMouseDown.bind(this)}
width={this.props.baseCanvas.width}
height={this.props.baseCanvas.height}
style={{width: this.props.baseCanvas.style.width,
height: this.props.baseCanvas.style.height}}></canvas>;
width={this.state.baseCanvasWidth}
height={this.state.baseCanvasHeight}
style={{width: this.state.canvasCssWidth,
height: this.state.canvasCssHeight}}></canvas>;
}

static getDerivedStateFromProps(nextProps, prevState) {
return {strokeStyle: nextProps.color, lineWidth: nextProps.lineWidth};
const newState = {
strokeStyle: nextProps.color,
lineWidth: nextProps.lineWidth,
baseCanvasWidth: nextProps.canvasCssWidth * nextProps.canvasPixelRatio,
baseCanvasHeight: nextProps.canvasCssHeight * nextProps.canvasPixelRatio,
canvasCssWidth: nextProps.canvasCssWidth,
canvasCssHeight: nextProps.canvasCssHeight
};
return newState;
}

componentDidMount() {
this.drawingContext = this.canvas.current.getContext("2d");
this.drawingContext.scale(this.props.devicePixelRatio, this.props.devicePixelRatio);
this.drawingContext.scale(this.props.canvasPixelRatio, this.props.canvasPixelRatio);
this.setDrawingProperties();
}

setDrawingProperties() {
console.warn("Please override setDrawingProperties in your component.");
}

componentDidUpdate() {
componentDidUpdate(oldProps, oldState) {
if (oldState.baseCanvasWidth !== this.state.baseCanvasWidth) {
this.drawingContext.scale(this.props.canvasPixelRatio, this.props.canvasPixelRatio);
}
this.setDrawingProperties();
}

Expand Down Expand Up @@ -92,10 +101,10 @@ exports.DrawingTool = class DrawingTool extends React.Component {
this.drawnArea.top = Math.ceil(Math.max(this.drawnArea.top - this.state.lineWidth, 0));
this.drawnArea.right = Math.ceil(Math.min(
this.drawnArea.right + this.state.lineWidth,
this.canvasWidth));
this.state.canvasCssWidth));
this.drawnArea.bottom = Math.ceil(Math.min(
this.drawnArea.bottom + this.state.lineWidth,
this.canvasHeight));
this.state.canvasCssHeight));

this.finalize();

Expand Down Expand Up @@ -124,7 +133,9 @@ exports.DrawingTool = class DrawingTool extends React.Component {

exports.DrawingTool.propTypes = {
baseCanvas: PropTypes.object,
devicePixelRatio: PropTypes.number,
canvasPixelRatio: PropTypes.number,
canvasCssWidth: PropTypes.number,
canvasCssHeight: PropTypes.number,
updateImageCallback: PropTypes.func,
color: PropTypes.string,
lineWidth: PropTypes.number,
Expand Down
30 changes: 15 additions & 15 deletions server/src/pages/shot/editor-history.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
const { Selection } = require("../../../shared/selection");

exports.EditorHistory = class {
constructor(devicePixelRatio) {
constructor(canvasPixelRatio) {
this.beforeEdits = [];
this.afterEdits = [];
this.devicePixelRatio = devicePixelRatio;
this.canvasPixelRatio = canvasPixelRatio;
}

push(canvas, area, recordType) {
const record = new EditRecord(
canvas,
area,
this.devicePixelRatio,
this.canvasPixelRatio,
recordType
);
this.beforeEdits.push(record);
Expand Down Expand Up @@ -65,7 +65,7 @@ exports.EditorHistory = class {
const toRecord = new EditRecord(
canvasBeforeChange,
area,
this.devicePixelRatio,
this.canvasPixelRatio,
fromRecord.recordType
);

Expand All @@ -76,37 +76,37 @@ exports.EditorHistory = class {
};

class EditRecord {
constructor(canvas, area, devicePixelRatio, recordType) {
constructor(canvas, area, canvasPixelRatio, recordType) {
this.area = area;
this.recordType = recordType;
this.canvas = this.captureCanvas(canvas, area, devicePixelRatio, recordType);
this.canvas = this.captureCanvas(canvas, area, canvasPixelRatio, recordType);
}

captureCanvas(canvas, area, devicePixelRatio, recordType) {
captureCanvas(canvas, area, canvasPixelRatio, recordType) {
const copy = document.createElement("canvas");

if (recordType === RecordType.FRAME) {
copy.width = canvas.width;
copy.height = canvas.height;
const copyContext = copy.getContext("2d");
copyContext.scale(devicePixelRatio, devicePixelRatio);
copyContext.scale(canvasPixelRatio, canvasPixelRatio);
copyContext.drawImage(
canvas,
0, 0, canvas.width, canvas.height,
0, 0, area.width, area.height);
return copy;
}

copy.width = area.width * devicePixelRatio;
copy.height = area.height * devicePixelRatio;
copy.width = area.width * canvasPixelRatio;
copy.height = area.height * canvasPixelRatio;
const copyContext = copy.getContext("2d");
copyContext.scale(devicePixelRatio, devicePixelRatio);
copyContext.scale(canvasPixelRatio, canvasPixelRatio);
copyContext.drawImage(
canvas,
area.left * devicePixelRatio,
area.top * devicePixelRatio,
area.width * devicePixelRatio,
area.height * devicePixelRatio,
area.left * canvasPixelRatio,
area.top * canvasPixelRatio,
area.width * canvasPixelRatio,
area.height * canvasPixelRatio,
0, 0, area.width, area.height
);

Expand Down
Loading

0 comments on commit 24e723b

Please sign in to comment.