Browse Source

add PNG export (#31)

David Luzar 5 years ago
parent
commit
68eeaa3c7d
2 changed files with 116 additions and 7 deletions
  1. 95 7
      src/index.js
  2. 21 0
      src/styles.css

+ 95 - 7
src/index.js

@@ -6,18 +6,70 @@ import "./styles.css";
 
 var elements = [];
 
-function newElement(type, x, y) {
+function newElement(type, x, y, width = 0, height = 0) {
   const element = {
     type: type,
     x: x,
     y: y,
-    width: 0,
-    height: 0,
+    width: width,
+    height: height,
     isSelected: false
   };
   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) {
   // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
   // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
@@ -150,7 +202,7 @@ function clearSelection() {
 class App extends React.Component {
   componentDidMount() {
     this.onKeyDown = event => {
-      if (event.key === "Backspace") {
+      if (event.key === "Backspace" && event.target.nodeName !== "INPUT") {
         for (var i = elements.length - 1; i >= 0; --i) {
           if (elements[i].isSelected) {
             elements.splice(i, 1);
@@ -188,7 +240,10 @@ class App extends React.Component {
     super();
     this.state = {
       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>
         {/* 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,
@@ -352,7 +440,7 @@ class App extends React.Component {
           }}
         />
       </div>
-    );
+    </>;
   }
 }
 

+ 21 - 0
src/styles.css

@@ -3,3 +3,24 @@
   font-family: "Virgil";
   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;
+}