add ellipse tool
This commit is contained in:
parent
7d3f414be9
commit
88d95d7c3a
|
|
@ -10,7 +10,10 @@
|
||||||
"@tauri-apps/cli": "^2"
|
"@tauri-apps/cli": "^2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ffmpeg/ffmpeg": "^0.12.10",
|
||||||
"@tauri-apps/plugin-dialog": "~2",
|
"@tauri-apps/plugin-dialog": "~2",
|
||||||
"@tauri-apps/plugin-fs": "~2"
|
"@tauri-apps/plugin-fs": "~2",
|
||||||
|
"ffmpeg": "^0.0.4",
|
||||||
|
"ffmpeg.js": "^4.2.9003"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,21 @@ importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@ffmpeg/ffmpeg':
|
||||||
|
specifier: ^0.12.10
|
||||||
|
version: 0.12.10
|
||||||
'@tauri-apps/plugin-dialog':
|
'@tauri-apps/plugin-dialog':
|
||||||
specifier: ~2
|
specifier: ~2
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
'@tauri-apps/plugin-fs':
|
'@tauri-apps/plugin-fs':
|
||||||
specifier: ~2
|
specifier: ~2
|
||||||
version: 2.0.2
|
version: 2.0.2
|
||||||
|
ffmpeg:
|
||||||
|
specifier: ^0.0.4
|
||||||
|
version: 0.0.4
|
||||||
|
ffmpeg.js:
|
||||||
|
specifier: ^4.2.9003
|
||||||
|
version: 4.2.9003
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tauri-apps/cli':
|
'@tauri-apps/cli':
|
||||||
specifier: ^2
|
specifier: ^2
|
||||||
|
|
@ -21,6 +30,14 @@ importers:
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
'@ffmpeg/ffmpeg@0.12.10':
|
||||||
|
resolution: {integrity: sha512-lVtk8PW8e+NUzGZhPTWj2P1J4/NyuCrbDD3O9IGpSeLYtUZKBqZO8CNj1WYGghep/MXoM8e1qVY1GztTkf8YYQ==}
|
||||||
|
engines: {node: '>=18.x'}
|
||||||
|
|
||||||
|
'@ffmpeg/types@0.12.2':
|
||||||
|
resolution: {integrity: sha512-NJtxwPoLb60/z1Klv0ueshguWQ/7mNm106qdHkB4HL49LXszjhjCCiL+ldHJGQ9ai2Igx0s4F24ghigy//ERdA==}
|
||||||
|
engines: {node: '>=16.x'}
|
||||||
|
|
||||||
'@tauri-apps/api@2.1.1':
|
'@tauri-apps/api@2.1.1':
|
||||||
resolution: {integrity: sha512-fzUfFFKo4lknXGJq8qrCidkUcKcH2UHhfaaCNt4GzgzGaW2iS26uFOg4tS3H4P8D6ZEeUxtiD5z0nwFF0UN30A==}
|
resolution: {integrity: sha512-fzUfFFKo4lknXGJq8qrCidkUcKcH2UHhfaaCNt4GzgzGaW2iS26uFOg4tS3H4P8D6ZEeUxtiD5z0nwFF0UN30A==}
|
||||||
|
|
||||||
|
|
@ -95,8 +112,23 @@ packages:
|
||||||
'@tauri-apps/plugin-fs@2.0.2':
|
'@tauri-apps/plugin-fs@2.0.2':
|
||||||
resolution: {integrity: sha512-4YZaX2j7ta81M5/DL8aN10kTnpUkEpkPo1FTYPT8Dd0ImHe3azM8i8MrtjrDGoyBYLPO3zFv7df/mSCYF8oA0Q==}
|
resolution: {integrity: sha512-4YZaX2j7ta81M5/DL8aN10kTnpUkEpkPo1FTYPT8Dd0ImHe3azM8i8MrtjrDGoyBYLPO3zFv7df/mSCYF8oA0Q==}
|
||||||
|
|
||||||
|
ffmpeg.js@4.2.9003:
|
||||||
|
resolution: {integrity: sha512-l1JBr8HwnnJEaSwg5p8K3Ifbom8O2IDHsZp7UVyr6MzQ7gc32tt/2apoOuQAr/j76c+uDOjla799VSsBnRvSTg==}
|
||||||
|
|
||||||
|
ffmpeg@0.0.4:
|
||||||
|
resolution: {integrity: sha512-3TgWUJJlZGQn+crJFyhsO/oNeRRnGTy6GhgS98oUCIfZrOW5haPPV7DUfOm3xJcHr5q3TJpjk2GudPutrNisRA==}
|
||||||
|
|
||||||
|
when@3.7.8:
|
||||||
|
resolution: {integrity: sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==}
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
|
'@ffmpeg/ffmpeg@0.12.10':
|
||||||
|
dependencies:
|
||||||
|
'@ffmpeg/types': 0.12.2
|
||||||
|
|
||||||
|
'@ffmpeg/types@0.12.2': {}
|
||||||
|
|
||||||
'@tauri-apps/api@2.1.1': {}
|
'@tauri-apps/api@2.1.1': {}
|
||||||
|
|
||||||
'@tauri-apps/cli-darwin-arm64@2.0.4':
|
'@tauri-apps/cli-darwin-arm64@2.0.4':
|
||||||
|
|
@ -149,3 +181,11 @@ snapshots:
|
||||||
'@tauri-apps/plugin-fs@2.0.2':
|
'@tauri-apps/plugin-fs@2.0.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.1.1
|
'@tauri-apps/api': 2.1.1
|
||||||
|
|
||||||
|
ffmpeg.js@4.2.9003: {}
|
||||||
|
|
||||||
|
ffmpeg@0.0.4:
|
||||||
|
dependencies:
|
||||||
|
when: 3.7.8
|
||||||
|
|
||||||
|
when@3.7.8: {}
|
||||||
|
|
|
||||||
112
src/main.js
112
src/main.js
|
|
@ -3,7 +3,7 @@ import * as fitCurve from '/fit-curve.js';
|
||||||
import { Bezier } from "/bezier.js";
|
import { Bezier } from "/bezier.js";
|
||||||
import { Quadtree } from './quadtree.js';
|
import { Quadtree } from './quadtree.js';
|
||||||
import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js';
|
import { createNewFileDialog, showNewFileDialog, closeDialog } from './newfile.js';
|
||||||
import { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels } from './utils.js';
|
import { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels, lerpColor, lerp } from './utils.js';
|
||||||
const { writeTextFile: writeTextFile, readTextFile: readTextFile, writeFile: writeFile }= window.__TAURI__.fs;
|
const { writeTextFile: writeTextFile, readTextFile: readTextFile, writeFile: writeFile }= window.__TAURI__.fs;
|
||||||
const {
|
const {
|
||||||
open: openFileDialog,
|
open: openFileDialog,
|
||||||
|
|
@ -276,28 +276,29 @@ let actions = {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
img = await loadImage(action.src)
|
img = await loadImage(action.src)
|
||||||
|
console.log(img.crossOrigin)
|
||||||
// img.onload = function() {
|
// img.onload = function() {
|
||||||
let ct = {
|
let ct = {
|
||||||
...context,
|
...context,
|
||||||
fillImage: img,
|
fillImage: img,
|
||||||
strokeShape: false,
|
strokeShape: false,
|
||||||
}
|
}
|
||||||
let imageShape = new Shape(0, 0, ct, action.shapeUuid)
|
let imageShape = new Shape(0, 0, ct, action.shapeUuid)
|
||||||
imageShape.addLine(img.width, 0)
|
imageShape.addLine(img.width, 0)
|
||||||
imageShape.addLine(img.width, img.height)
|
imageShape.addLine(img.width, img.height)
|
||||||
imageShape.addLine(0, img.height)
|
imageShape.addLine(0, img.height)
|
||||||
imageShape.addLine(0, 0)
|
imageShape.addLine(0, 0)
|
||||||
imageShape.update()
|
imageShape.update()
|
||||||
imageShape.fillImage = img
|
imageShape.fillImage = img
|
||||||
imageShape.filled = true
|
imageShape.filled = true
|
||||||
imageObject.addShape(imageShape)
|
imageObject.addShape(imageShape)
|
||||||
let parent = pointerList[action.parent]
|
let parent = pointerList[action.parent]
|
||||||
parent.addObject(
|
parent.addObject(
|
||||||
imageObject,
|
imageObject,
|
||||||
action.x-img.width/2 + (20*action.ix),
|
action.x-img.width/2 + (20*action.ix),
|
||||||
action.y-img.height/2 + (20*action.ix)
|
action.y-img.height/2 + (20*action.ix)
|
||||||
)
|
)
|
||||||
updateUI();
|
updateUI();
|
||||||
// }
|
// }
|
||||||
// img.src = action.src
|
// img.src = action.src
|
||||||
},
|
},
|
||||||
|
|
@ -959,6 +960,7 @@ class BaseShape {
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
}
|
}
|
||||||
if (this.stroked) {
|
if (this.stroked) {
|
||||||
|
console.log(this.curves)
|
||||||
for (let curve of this.curves) {
|
for (let curve of this.curves) {
|
||||||
ctx.strokeStyle = curve.color
|
ctx.strokeStyle = curve.color
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
|
|
@ -968,9 +970,11 @@ class BaseShape {
|
||||||
curve.points[3].x, curve.points[3].y)
|
curve.points[3].x, curve.points[3].y)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
|
|
||||||
// Debug, show curve endpoints
|
// // Debug, show curve control points
|
||||||
// ctx.beginPath()
|
// ctx.beginPath()
|
||||||
// ctx.arc(curve.points[3].x,curve.points[3].y, 3, 0, 2*Math.PI)
|
// ctx.arc(curve.points[1].x,curve.points[1].y, 5, 0, 2*Math.PI)
|
||||||
|
// ctx.arc(curve.points[2].x,curve.points[2].y, 5, 0, 2*Math.PI)
|
||||||
|
// ctx.arc(curve.points[3].x,curve.points[3].y, 5, 0, 2*Math.PI)
|
||||||
// ctx.fill()
|
// ctx.fill()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -981,12 +985,14 @@ class BaseShape {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TempShape extends BaseShape {
|
class TempShape extends BaseShape {
|
||||||
constructor(startx, starty, curves, lineWidth, stroked, filled) {
|
constructor(startx, starty, curves, lineWidth, stroked, filled, strokeStyle, fillStyle) {
|
||||||
super(startx, starty)
|
super(startx, starty)
|
||||||
this.curves = curves
|
this.curves = curves
|
||||||
this.lineWidth = lineWidth
|
this.lineWidth = lineWidth
|
||||||
this.stroked = stroked
|
this.stroked = stroked
|
||||||
this.filled = filled
|
this.filled = filled
|
||||||
|
this.strokeStyle = strokeStyle
|
||||||
|
this.fillStyle = fillStyle
|
||||||
this.inProgress = false
|
this.inProgress = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1380,8 +1386,6 @@ class GraphicsObject {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(path1)
|
|
||||||
console.log(path2)
|
|
||||||
const interpolator = d3.interpolatePathCommands(path1, path2)
|
const interpolator = d3.interpolatePathCommands(path1, path2)
|
||||||
let current = interpolator(t)
|
let current = interpolator(t)
|
||||||
let curves = []
|
let curves = []
|
||||||
|
|
@ -1392,9 +1396,16 @@ class GraphicsObject {
|
||||||
x = curve.x
|
x = curve.x
|
||||||
y = curve.y
|
y = curve.y
|
||||||
}
|
}
|
||||||
console.log(curves)
|
let lineWidth = lerp(shape1.lineWidth, shape2.lineWidth, t)
|
||||||
// TODO: lerp lineWidth
|
let strokeStyle = lerpColor(shape1.strokeStyle, shape2.strokeStyle, t)
|
||||||
shapes.push(new TempShape(start.x, start.y, curves, shape1.lineWidth, shape1.stroked))
|
let fillStyle;
|
||||||
|
if (!shape1.fillImage) {
|
||||||
|
fillStyle = lerpColor(shape1.fillStyle, shape2.fillStyle, t)
|
||||||
|
}
|
||||||
|
shapes.push(new TempShape(
|
||||||
|
start.x, start.y, curves, shape1.lineWidth,
|
||||||
|
shape1.stroked, shape1.filled, strokeStyle, fillStyle
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let frame = new Frame("shape", "temp")
|
let frame = new Frame("shape", "temp")
|
||||||
|
|
@ -1899,7 +1910,7 @@ async function render() {
|
||||||
exportContext.ctx.fillStyle = "white"
|
exportContext.ctx.fillStyle = "white"
|
||||||
exportContext.ctx.rect(0,0,fileWidth, fileHeight)
|
exportContext.ctx.rect(0,0,fileWidth, fileHeight)
|
||||||
exportContext.ctx.fill()
|
exportContext.ctx.fill()
|
||||||
root.draw(exportContext)
|
await root.draw(exportContext)
|
||||||
|
|
||||||
// Convert the canvas content to a PNG image (this is the "frame" we add to the APNG)
|
// Convert the canvas content to a PNG image (this is the "frame" we add to the APNG)
|
||||||
const imageData = exportContext.ctx.getImageData(0, 0, canvas.width, canvas.height);
|
const imageData = exportContext.ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
|
@ -1983,7 +1994,7 @@ function stage() {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
let mouse = getMousePos(stage, e)
|
let mouse = getMousePos(stage, e)
|
||||||
const imageTypes = ['image/png', 'image/gif', 'image/avif', 'image/jpeg',
|
const imageTypes = ['image/png', 'image/gif', 'image/avif', 'image/jpeg',
|
||||||
'image/svg+xml', 'image/webp'
|
'image/webp', //'image/svg+xml' // Disabling SVG until we can export them nicely
|
||||||
];
|
];
|
||||||
if (e.dataTransfer.items) {
|
if (e.dataTransfer.items) {
|
||||||
let i = 0
|
let i = 0
|
||||||
|
|
@ -2029,6 +2040,7 @@ function stage() {
|
||||||
let mouse = getMousePos(stage, e)
|
let mouse = getMousePos(stage, e)
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
|
case "ellipse":
|
||||||
case "draw":
|
case "draw":
|
||||||
context.mouseDown = true
|
context.mouseDown = true
|
||||||
context.activeShape = new Shape(mouse.x, mouse.y, context, true, true)
|
context.activeShape = new Shape(mouse.x, mouse.y, context, true, true)
|
||||||
|
|
@ -2141,6 +2153,7 @@ function stage() {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
|
case "ellipse":
|
||||||
actions.addShape.create(context.activeObject, context.activeShape)
|
actions.addShape.create(context.activeObject, context.activeShape)
|
||||||
context.activeShape = undefined
|
context.activeShape = undefined
|
||||||
break;
|
break;
|
||||||
|
|
@ -2205,6 +2218,41 @@ function stage() {
|
||||||
context.activeShape.update()
|
context.activeShape.update()
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "ellipse":
|
||||||
|
context.activeCurve = undefined
|
||||||
|
if (context.activeShape) {
|
||||||
|
let midX = (mouse.x + context.activeShape.startx) / 2
|
||||||
|
let midY = (mouse.y + context.activeShape.starty) / 2
|
||||||
|
let xDiff = (mouse.x - context.activeShape.startx) / 2
|
||||||
|
let yDiff = (mouse.y - context.activeShape.starty) / 2
|
||||||
|
let ellipseConst = 0.552284749831
|
||||||
|
context.activeShape.clear()
|
||||||
|
context.activeShape.addCurve(new Bezier(
|
||||||
|
midX, context.activeShape.starty,
|
||||||
|
midX + ellipseConst * xDiff, context.activeShape.starty,
|
||||||
|
mouse.x, midY - ellipseConst * yDiff,
|
||||||
|
mouse.x, midY
|
||||||
|
))
|
||||||
|
context.activeShape.addCurve(new Bezier(
|
||||||
|
mouse.x, midY,
|
||||||
|
mouse.x, midY + ellipseConst * yDiff,
|
||||||
|
midX + ellipseConst * xDiff, mouse.y,
|
||||||
|
midX, mouse.y
|
||||||
|
))
|
||||||
|
context.activeShape.addCurve(new Bezier(
|
||||||
|
midX, mouse.y,
|
||||||
|
midX - ellipseConst * xDiff, mouse.y,
|
||||||
|
context.activeShape.startx, midY + ellipseConst * yDiff,
|
||||||
|
context.activeShape.startx, midY
|
||||||
|
))
|
||||||
|
context.activeShape.addCurve(new Bezier(
|
||||||
|
context.activeShape.startx, midY,
|
||||||
|
context.activeShape.startx, midY - ellipseConst * yDiff,
|
||||||
|
midX - ellipseConst * xDiff, context.activeShape.starty,
|
||||||
|
midX, context.activeShape.starty
|
||||||
|
))
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "select":
|
case "select":
|
||||||
if (context.dragging) {
|
if (context.dragging) {
|
||||||
if (context.activeVertex) {
|
if (context.activeVertex) {
|
||||||
|
|
|
||||||
33
src/utils.js
33
src/utils.js
|
|
@ -91,4 +91,35 @@ function invertPixels(ctx, width, height) {
|
||||||
ctx.globalCompositeOperation = "source-over"
|
ctx.globalCompositeOperation = "source-over"
|
||||||
}
|
}
|
||||||
|
|
||||||
export { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels };
|
function lerp(a, b, t) {
|
||||||
|
return a + (b - a) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lerpColor(color1, color2, t) {
|
||||||
|
// Convert hex color to RGB
|
||||||
|
const hexToRgb = (hex) => {
|
||||||
|
const r = parseInt(hex.slice(1, 3), 16);
|
||||||
|
const g = parseInt(hex.slice(3, 5), 16);
|
||||||
|
const b = parseInt(hex.slice(5, 7), 16);
|
||||||
|
return { r, g, b };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert RGB to hex color
|
||||||
|
const rgbToHex = (r, g, b) => {
|
||||||
|
return `#${(1 << 24 | (r << 16) | (g << 8) | b).toString(16).slice(1).toUpperCase()}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get RGB values of both colors
|
||||||
|
const start = hexToRgb(color1);
|
||||||
|
const end = hexToRgb(color2);
|
||||||
|
|
||||||
|
// Calculate the interpolated RGB values
|
||||||
|
const r = Math.round(start.r + (end.r - start.r) * t);
|
||||||
|
const g = Math.round(start.g + (end.g - start.g) * t);
|
||||||
|
const b = Math.round(start.b + (end.b - start.b) * t);
|
||||||
|
|
||||||
|
// Convert the interpolated RGB back to hex
|
||||||
|
return rgbToHex(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels, lerp, lerpColor };
|
||||||
Loading…
Reference in New Issue