Skip to content

Commit

Permalink
Can paint
Browse files Browse the repository at this point in the history
  • Loading branch information
nacmartin committed Mar 27, 2023
1 parent 6ef4fbd commit 92fb1ca
Show file tree
Hide file tree
Showing 14 changed files with 701 additions and 13 deletions.
24 changes: 24 additions & 0 deletions examples/paint/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
13 changes: 13 additions & 0 deletions examples/paint/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
20 changes: 20 additions & 0 deletions examples/paint/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "paint",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"@types/tinycolor2": "^1.4.3",
"typescript": "^4.9.3",
"vite": "^4.2.0"
},
"dependencies": {
"manitas": "workspace:^1.0.0",
"tinycolor2": "^1.6.0"
}
}
122 changes: 122 additions & 0 deletions examples/paint/src/brush.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { getNewAngle, rotatePoint, varyColour } from "./brushHelpers";

let currentAngle: number | null = null;
let lastPoint: Point | null = null;
let strokeWidth: number = 25;
let varyBrightness: number = 5;
let brush: Brush | null = null;

function startStroke(point: Point, color: string) {
currentAngle = null;
brush = makeBrush(strokeWidth, color, varyBrightness);
lastPoint = point;
}

function continueStroke(newPoint: Point, context: CanvasRenderingContext2D) {
if (!lastPoint) {
lastPoint = newPoint;
return;
}
const newAngle = getNewAngle(lastPoint, newPoint, currentAngle);
if (currentAngle === null) {
currentAngle = newAngle % (Math.PI * 2);
}
if (brush !== null) {
drawStroke(
brush,
lastPoint,
newPoint,
currentAngle,
newAngle,
strokeWidth,
context
);
}
currentAngle = newAngle % (Math.PI * 2);
lastPoint = newPoint;
}

function makeBrush(
strokeWidth: number,
colour: string,
varyBrightness: number
) {
const brush: Brush = [];
const bristleCount = Math.round(strokeWidth / 3);
const gap = strokeWidth / bristleCount;
for (let i = 0; i < bristleCount; i++) {
const distance =
i === 0 ? 0 : gap * i + (Math.random() * gap) / 2 - gap / 2;
brush.push({
distance,
thickness: Math.random() * 2 + 2,
colour: varyColour(colour, varyBrightness),
});
}
return brush;
}

const strokeBristle = (
origin: Point,
destination: Point,
bristle: Bristle,
controlPoint: Point,
context: CanvasRenderingContext2D
) => {
context.beginPath();
context.moveTo(origin[0], origin[1]);
context.strokeStyle = bristle.colour;
context.lineWidth = bristle.thickness;
context.lineCap = "round";
context.lineJoin = "round";
context.shadowColor = bristle.colour;
context.shadowBlur = bristle.thickness / 2;
context.quadraticCurveTo(
controlPoint[0],
controlPoint[1],
destination[0],
destination[1]
);
context.lineTo(destination[0], destination[1]);
context.stroke();
};

const drawStroke = (
bristles: Brush,
origin: Point,
destination: Point,
oldAngle: number,
newAngle: number,
strokeWidth: number,
context: CanvasRenderingContext2D
) => {
bristles.forEach((bristle) => {
context.beginPath();
const bristleOrigin = rotatePoint(
bristle.distance - strokeWidth / 2,
oldAngle,
origin
);

const bristleDestination = rotatePoint(
bristle.distance - strokeWidth / 2,
newAngle,
destination
);
const controlPoint = rotatePoint(
bristle.distance - strokeWidth / 2,
newAngle,
origin
);

strokeBristle(
bristleOrigin,
bristleDestination,
bristle,
controlPoint,
context
);
});
};

export { startStroke, continueStroke };
43 changes: 43 additions & 0 deletions examples/paint/src/brushHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import tinycolor from "tinycolor2";

export function varyColour(sourceColour: string, varyBrightness: number) {
const amount = Math.round(Math.random() * varyBrightness);
const alpha = 0.8 - Math.random() / 4;
const colour = tinycolor(sourceColour);
const varied = colour.darken(amount - varyBrightness / 2).setAlpha(alpha);
return varied.toPercentageRgbString();
}

export const rotatePoint = (
distance: number,
angle: number,
origin: Point
): Point => [
origin[0] + distance * Math.cos(angle),
origin[1] + distance * Math.sin(angle),
];

export const getBearing = (origin: Point, destination: Point) =>
(Math.atan2(destination[1] - origin[1], destination[0] - origin[0]) -
Math.PI / 2) %
(Math.PI * 2);

export const getNewAngle = (
origin: Point,
destination: Point,
oldAngle: number | null
) => {
const bearing = getBearing(origin, destination);
if (oldAngle === null) {
return bearing;
}
return oldAngle - angleDiff(oldAngle, bearing);
};

export const angleDiff = (angleA: number, angleB: number) => {
const twoPi = Math.PI * 2;
const diff =
((angleA - (angleB > 0 ? angleB : angleB + twoPi) + Math.PI) % twoPi) -
Math.PI;
return diff < -Math.PI ? diff + twoPi : diff;
};
34 changes: 34 additions & 0 deletions examples/paint/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { setupPaint } from "./paint";
import "./style.css";
import typescriptLogo from "./typescript.svg";
import viteLogo from "/vite.svg";

document.querySelector<HTMLDivElement>("#root")!.innerHTML = `
<center>
<h1>Manitas React Example</h1>
<p>
(This example is much better in desktop, not ready for mobile yet!)
</p>
</center>
<div class="center">
<video
id="webcam"
autoPlay
playsInline
style="height: 960px; width: 1280px"
class="cam"
>
</video>
<canvas
id="canvas"
class="canvas"
width=1280 height=960
>
</canvas>
</div>
<div>
`;

setupPaint(document.querySelector<HTMLCanvasElement>("#canvas"));

//setupCounter(document.querySelector<HTMLButtonElement>("#counter")!);
65 changes: 65 additions & 0 deletions examples/paint/src/paint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { init, AirfingerEvent } from "manitas";
import tinycolor from "tinycolor2";
import { continueStroke, startStroke } from "./brush";

const WIDTH = 1280;
const HEIGHT = 960;

export function setupPaint(canvasElement: HTMLCanvasElement | null) {
if (!canvasElement) {
console.warn("Unable to find canvas element");
return;
}
init({ delegate: "GPU" });
const canvasCtx = canvasElement.getContext("2d");
if (!canvasCtx) {
console.warn("Unable to get canvas context");
}
const document: Document = window.document;
document.addEventListener("airfingerstart", airfingerStart);
document.addEventListener("airfingermove", airfingerMove(canvasCtx));
document.addEventListener("airfingerend", airfingerEnd);
setupPallete(canvasCtx as CanvasRenderingContext2D);
}

function airfingerStart(event: Event) {
const detail = (event as AirfingerEvent).detail;
const { x, y } = detail.airpoint;
startStroke([x * WIDTH, y * HEIGHT], colors[colorIdx]);
}

function airfingerMove(canvasCtx: CanvasRenderingContext2D) {
return function (event: Event) {
const detail = (event as AirfingerEvent).detail;
const { x, y } = detail.airpoint;
continueStroke([x * WIDTH, y * HEIGHT], canvasCtx);
};
}

function airfingerEnd(event: Event) {
const detail = (event as AirfingerEvent).detail;
//console.log(detail);
}

let colorIdx = 0;

function setupPallete(canvasCtx: CanvasRenderingContext2D) {
canvasCtx.clearRect(5, 15, 160, 480);
colors.forEach((color, idx) => {
console.log(idx);
const colorPallete = tinycolor(color);
canvasCtx.fillStyle =
idx === colorIdx
? colorPallete.setAlpha(0.9).toString()
: colorPallete.setAlpha(0.5).toString();
console.log(canvasCtx.fillStyle);
canvasCtx.fillRect(10, 120 * idx + 30, 150, 100);
});
}

const colors = [
"rgb(0, 161, 157, 0.5)",
"rgb(255, 248, 229, 0.5)",
"rgb(255, 179, 68,0.5)",
"rgb(224, 93, 93,0.5)",
];
58 changes: 58 additions & 0 deletions examples/paint/src/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.deck {
position: absolute;
width: 300px;
height: 200px;
will-change: transform;
display: flex;
align-items: center;
justify-content: center;
touch-action: none;
}

.deck > div {
background-size: auto 85%;
background-repeat: no-repeat;
background-position: center center;
width: 270px;
height: 202px;
will-change: transform;
opacity: 0.9;
border-radius: 10px;
box-shadow: 0 12.5px 100px -10px rgba(50, 50, 73, 0.4), 0 10px 10px -10px rgba(50, 50, 73, 0.3);
position: absolute;
}

.deck > div > video {
background-size: auto 85%;
background-repeat: no-repeat;
background-position: center center;
width: 100%;
height: 100%;
will-change: transform;
border-radius: 10px;
box-shadow: 0 12.5px 100px -10px rgba(50, 50, 73, 0.4), 0 10px 10px -10px rgba(50, 50, 73, 0.3);
position: absolute;
}

/*.cam {
/* position: absolute;
/*}*/

.container {
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.center {
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
position: relative;
}

.canvas {
position: absolute;
}
8 changes: 8 additions & 0 deletions examples/paint/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
declare type Point = [number, number];

declare interface Bristle {
distance: number;
thickness: number;
colour: string;
}
declare type Brush = Bristle[];
1 change: 1 addition & 0 deletions examples/paint/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
Loading

0 comments on commit 92fb1ca

Please sign in to comment.