mirror of
https://github.com/excalidraw/excalidraw.git
synced 2024-11-02 03:25:53 +01:00
add PNG export (#31)
This commit is contained in:
parent
bb151d83bc
commit
68eeaa3c7d
102
src/index.js
102
src/index.js
@ -6,18 +6,70 @@ import "./styles.css";
|
|||||||
|
|
||||||
var elements = [];
|
var elements = [];
|
||||||
|
|
||||||
function newElement(type, x, y) {
|
function newElement(type, x, y, width = 0, height = 0) {
|
||||||
const element = {
|
const element = {
|
||||||
type: type,
|
type: type,
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
width: 0,
|
width: width,
|
||||||
height: 0,
|
height: height,
|
||||||
isSelected: false
|
isSelected: false
|
||||||
};
|
};
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exportAsPNG({ background, visibleOnly, padding = 10 }) {
|
||||||
|
clearSelection();
|
||||||
|
drawScene();
|
||||||
|
|
||||||
|
let subCanvasX1 = Infinity;
|
||||||
|
let subCanvasX2 = 0;
|
||||||
|
let subCanvasY1 = Infinity;
|
||||||
|
let subCanvasY2 = 0;
|
||||||
|
|
||||||
|
elements.forEach(element => {
|
||||||
|
subCanvasX1 = Math.min(subCanvasX1, getElementAbsoluteX1(element));
|
||||||
|
subCanvasX2 = Math.max(subCanvasX2, getElementAbsoluteX2(element));
|
||||||
|
subCanvasY1 = Math.min(subCanvasY1, getElementAbsoluteY1(element));
|
||||||
|
subCanvasY2 = Math.max(subCanvasY2, getElementAbsoluteY2(element));
|
||||||
|
});
|
||||||
|
|
||||||
|
let targetCanvas = canvas;
|
||||||
|
|
||||||
|
if ( visibleOnly ) {
|
||||||
|
targetCanvas = document.createElement('canvas');
|
||||||
|
targetCanvas.style.display = 'none';
|
||||||
|
document.body.appendChild(targetCanvas);
|
||||||
|
targetCanvas.width = subCanvasX2 - subCanvasX1 + padding * 2;
|
||||||
|
targetCanvas.height = subCanvasY2 - subCanvasY1 + padding * 2;
|
||||||
|
const targetCanvas_ctx = targetCanvas.getContext('2d');
|
||||||
|
|
||||||
|
if ( background ) {
|
||||||
|
targetCanvas_ctx.fillStyle = "#FFF";
|
||||||
|
targetCanvas_ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
targetCanvas_ctx.drawImage(
|
||||||
|
canvas,
|
||||||
|
subCanvasX1 - padding, // x
|
||||||
|
subCanvasY1 - padding, // y
|
||||||
|
subCanvasX2 - subCanvasX1 + padding * 2, // width
|
||||||
|
subCanvasY2 - subCanvasY1 + padding * 2, // height
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
targetCanvas.width,
|
||||||
|
targetCanvas.height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.setAttribute('download', 'excalibur.png');
|
||||||
|
link.setAttribute('href', targetCanvas.toDataURL("image/png"));
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
if ( targetCanvas !== canvas ) targetCanvas.remove();
|
||||||
|
}
|
||||||
|
|
||||||
function rotate(x1, y1, x2, y2, angle) {
|
function rotate(x1, y1, x2, y2, angle) {
|
||||||
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
||||||
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
||||||
@ -150,7 +202,7 @@ function clearSelection() {
|
|||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.onKeyDown = event => {
|
this.onKeyDown = event => {
|
||||||
if (event.key === "Backspace") {
|
if (event.key === "Backspace" && event.target.nodeName !== "INPUT") {
|
||||||
for (var i = elements.length - 1; i >= 0; --i) {
|
for (var i = elements.length - 1; i >= 0; --i) {
|
||||||
if (elements[i].isSelected) {
|
if (elements[i].isSelected) {
|
||||||
elements.splice(i, 1);
|
elements.splice(i, 1);
|
||||||
@ -188,7 +240,10 @@ class App extends React.Component {
|
|||||||
super();
|
super();
|
||||||
this.state = {
|
this.state = {
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
elementType: "selection"
|
elementType: "selection",
|
||||||
|
exportBackground: false,
|
||||||
|
exportVisibleOnly: true,
|
||||||
|
exportPadding: 10
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +265,40 @@ class App extends React.Component {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <>
|
||||||
|
<div className="exportWrapper">
|
||||||
|
<button onClick={() => {
|
||||||
|
exportAsPNG({
|
||||||
|
background: this.state.exportBackground,
|
||||||
|
visibleOnly: this.state.exportVisibleOnly,
|
||||||
|
padding: this.state.exportPadding
|
||||||
|
})
|
||||||
|
}}>Export to png</button>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
checked={this.state.exportBackground}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ exportBackground: e.target.checked })
|
||||||
|
}}
|
||||||
|
/> background
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
checked={this.state.exportVisibleOnly}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ exportVisibleOnly: e.target.checked })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
visible area only
|
||||||
|
</label>
|
||||||
|
(padding:
|
||||||
|
<input type="number" value={this.state.exportPadding}
|
||||||
|
onChange={e => {
|
||||||
|
this.setState({ exportPadding: e.target.value });
|
||||||
|
}}
|
||||||
|
disabled={!this.state.exportVisibleOnly}/>
|
||||||
|
px)
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{/* Can't use the <ElementOption> form because ElementOption is re-defined
|
{/* Can't use the <ElementOption> form because ElementOption is re-defined
|
||||||
on every render, which would blow up and re-create the entire DOM tree,
|
on every render, which would blow up and re-create the entire DOM tree,
|
||||||
@ -352,7 +440,7 @@ class App extends React.Component {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
</>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,3 +3,24 @@
|
|||||||
font-family: "Virgil";
|
font-family: "Virgil";
|
||||||
src: url("https://uploads.codesandbox.io/uploads/user/ed077012-e728-4a42-8395-cbd299149d62/AflB-FG_Virgil.ttf");
|
src: url("https://uploads.codesandbox.io/uploads/user/ed077012-e728-4a42-8395-cbd299149d62/AflB-FG_Virgil.ttf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.exportWrapper {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.exportWrapper label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exportWrapper button {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exportWrapper input[type="number"] {
|
||||||
|
width: 40px;
|
||||||
|
padding: 2px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user