Added circle and rect outline options and optimised storage structure

This commit is contained in:
Mitchell McCaffrey 2021-04-25 17:10:32 +10:00
parent f7975c041a
commit 994b9b5ebb
4 changed files with 43 additions and 67 deletions

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { Image as KonvaImage, Group, Line } from "react-konva"; import { Image as KonvaImage, Group, Line, Rect, Circle } from "react-konva";
import { useSpring, animated } from "react-spring/konva"; import { useSpring, animated } from "react-spring/konva";
import useImage from "use-image"; import useImage from "use-image";
import Konva from "konva"; import Konva from "konva";
@ -246,6 +246,37 @@ function MapToken({
tokenName = tokenName + "-locked"; tokenName = tokenName + "-locked";
} }
let outline = tokenState.outline;
if (Array.isArray(tokenState.outline)) {
outline = [...outline]; // Copy array so we can edit it imutably
for (let i = 0; i < outline.length; i += 2) {
// Scale outline to the token
outline[i] = (outline[i] / tokenState.width) * tokenWidth;
outline[i + 1] = (outline[i + 1] / tokenState.height) * tokenHeight;
}
}
function renderOutline() {
const sharedProps = {
fill: colors.black,
width: tokenWidth,
height: tokenHeight,
x: 0,
y: 0,
rotation: tokenState.rotation,
offsetX: tokenWidth / 2,
offsetY: tokenHeight / 2,
opacity: 0.8,
};
if (outline === "rect") {
return <Rect {...sharedProps} />;
} else if (outline === "circle") {
return <Circle {...sharedProps} offsetX={0} offsetY={0} />;
} else {
return <Line {...sharedProps} points={outline} closed tension={0.33} />;
}
}
return ( return (
<animated.Group <animated.Group
{...props} {...props}
@ -268,24 +299,7 @@ function MapToken({
id={tokenState.id} id={tokenState.id}
> >
{!tokenImage ? ( {!tokenImage ? (
<Group opacity={0.8}> renderOutline()
<Line
points={tokenState.outline
.map(({ x, y }) => [x * tokenWidth, y * tokenHeight])
.flat(1)}
fill={colors.black}
width={tokenWidth}
height={tokenHeight}
x={0}
y={0}
rotation={tokenState.rotation}
offsetX={tokenWidth / 2}
offsetY={tokenHeight / 2}
closed
// Round edges for more complex shapes
tension={tokenState.outline.length > 6 ? 0.333 : 0}
/>
</Group>
) : ( ) : (
<KonvaImage <KonvaImage
ref={imageRef} ref={imageRef}

View File

@ -4,13 +4,11 @@ import "dexie-observable";
import shortid from "shortid"; import shortid from "shortid";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import Case from "case"; import Case from "case";
import imageOutline from "image-outline";
import blobToBuffer from "./helpers/blobToBuffer"; import blobToBuffer from "./helpers/blobToBuffer";
import { getGridDefaultInset } from "./helpers/grid"; import { getGridDefaultInset } from "./helpers/grid";
import { convertOldActionsToShapes } from "./actions"; import { convertOldActionsToShapes } from "./actions";
import { createThumbnail } from "./helpers/image"; import { createThumbnail } from "./helpers/image";
import Vector2 from "./helpers/Vector2";
// Helper to create a thumbnail for a file in a db // Helper to create a thumbnail for a file in a db
async function createDataThumbnail(data) { async function createDataThumbnail(data) {
@ -571,23 +569,9 @@ const versions = {
delete token.category; delete token.category;
token.defaultLabel = ""; token.defaultLabel = "";
if (token.width === token.height) { if (token.width === token.height) {
token.outline = [ token.outline = "circle";
{ x: 0.5, y: 0.0 },
{ x: 0.85, y: 0.15 },
{ x: 1.0, y: 0.5 },
{ x: 0.85, y: 0.85 },
{ x: 0.5, y: 1.0 },
{ x: 0.15, y: 0.85 },
{ x: 0.0, y: 0.5 },
{ x: 0.15, y: 0.15 },
];
} else { } else {
token.outline = [ token.outline = "rect";
{ x: 0.0, y: 0.0 },
{ x: 1.0, y: 0.0 },
{ x: 1.0, y: 1.0 },
{ x: 0.0, y: 1.0 },
];
} }
}); });
}); });
@ -612,12 +596,7 @@ const versions = {
tokenState.category = "character"; tokenState.category = "character";
tokenState.type = "file"; tokenState.type = "file";
tokenState.file = ""; tokenState.file = "";
tokenState.outline = [ tokenState.outline = "rect";
{ x: 0.0, y: 0.0 },
{ x: 1.0, y: 0.0 },
{ x: 1.0, y: 1.0 },
{ x: 0.0, y: 1.0 },
];
tokenState.width = 256; tokenState.width = 256;
tokenState.height = 256; tokenState.height = 256;
} }
@ -625,16 +604,7 @@ const versions = {
tokenState.category = "character"; tokenState.category = "character";
tokenState.type = "default"; tokenState.type = "default";
tokenState.key = Case.camel(tokenState.tokenId.slice(10)); tokenState.key = Case.camel(tokenState.tokenId.slice(10));
tokenState.outline = [ tokenState.outline = "circle";
{ x: 0.5, y: 0.0 },
{ x: 0.85, y: 0.15 },
{ x: 1.0, y: 0.5 },
{ x: 0.85, y: 0.85 },
{ x: 0.5, y: 1.0 },
{ x: 0.15, y: 0.85 },
{ x: 0.0, y: 0.5 },
{ x: 0.15, y: 0.15 },
];
tokenState.width = 256; tokenState.width = 256;
tokenState.height = 256; tokenState.height = 256;
} }

View File

@ -179,13 +179,14 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
}; };
assets.push(fileAsset); assets.push(fileAsset);
let outline = imageOutline(image).map(({ x, y }) => ({ let outline = imageOutline(image);
x: x / image.width,
y: y / image.height,
}));
if (outline.length > 100) { if (outline.length > 100) {
outline = Vector2.resample(outline, 100); outline = Vector2.resample(outline, 100);
} }
// Flatten and round outline to save on storage size
outline = outline
.map(({ x, y }) => [Math.round(x), Math.round(y)])
.flat();
const token = { const token = {
name, name,

View File

@ -90,16 +90,7 @@ export const tokens = Object.keys(tokenSources).map((key) => ({
hideInSidebar: false, hideInSidebar: false,
width: 256, width: 256,
height: 256, height: 256,
outline: [ outline: "circle",
{ x: 0.5, y: 0.0 },
{ x: 0.85, y: 0.15 },
{ x: 1.0, y: 0.5 },
{ x: 0.85, y: 0.85 },
{ x: 0.5, y: 1.0 },
{ x: 0.15, y: 0.85 },
{ x: 0.0, y: 0.5 },
{ x: 0.15, y: 0.15 },
],
})); }));
export const unknownSource = unknown; export const unknownSource = unknown;