提交 a7e16da8 编写于 作者: Q qq_41923622

Fri Jan 24 10:44:00 CST 2025 inscode

上级 669eac6d
run = "npm i && npm run dev"
language = "node"
[deployment]
build = "npm i && npm run build"
run = "npm run preview"
[env]
PATH = "/root/${PROJECT_DIR}/.config/npm/node_global/bin:/root/${PROJECT_DIR}/node_modules/.bin:${PATH}"
XDG_CONFIG_HOME = "/root/.config"
......
basic.jpg

480.7 KB

export function setupCounter(element: HTMLButtonElement): void
<!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 App</title>
</head>
<style>
html,body{
margin: 0;
border: 0;
height: 100%;
}
</style>
<body style="background-color: gainsboro;">
<div id="app"></div>
<script type="module" src="src/main.ts"></script>
</body>
</html>
console.log("欢迎来到 InsCode");
\ No newline at end of file
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/node": "^18.0.6",
"node-fetch": "^3.2.6"
}
"name": "online-image-editor",
"private": true,
"version": "0.0.0",
"type": "module",
"files": [
"dist",
"index.d.ts"
],
"main": "./dist/counter.umd.cjs",
"module": "./dist/counter.js",
"types": "./index.d.ts",
"exports": {
"types": "./index.d.ts",
"import": "./dist/counter.js",
"require": "./dist/counter.umd.cjs"
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build"
},
"devDependencies": {
"typescript": "^5.4.5",
"vite": "^5.4.8",
"vite-plugin-copy": "^0.1.6",
"vite-plugin-static-copy": "^2.0.0"
},
"dependencies": {
"fabric": "^6.4.3",
"rollup-plugin-copy": "^3.5.0"
}
\ No newline at end of file
}
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703559162011" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1064" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 96c229.76 0 416 186.24 416 416s-186.24 416-416 416S96 741.76 96 512 282.24 96 512 96zM526.933333 298.666667h-29.866666a17.066667 17.066667 0 0 0-17.066667 17.066666v164.266667H315.733333a17.066667 17.066667 0 0 0-17.066666 17.066667v29.866666c0 9.386667 7.68 17.066667 17.066666 17.066667h164.266667v164.266667c0 9.386667 7.68 17.066667 17.066667 17.066666h29.866666a17.066667 17.066667 0 0 0 17.066667-17.066666v-164.266667h164.266667a17.066667 17.066667 0 0 0 17.066666-17.066667v-29.866666a17.066667 17.066667 0 0 0-17.066666-17.066667h-164.266667V315.733333a17.066667 17.066667 0 0 0-17.066667-17.066666z" fill="#515151" fill-opacity=".87" p-id="1065"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1695113090426" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12842" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M822.7 767.6L626.4 571.3l163.1-90.6c35.1-19.5 32-71-5.3-86.1l-551.6-223c-39.1-15.8-78 23.1-62.2 62.2l223 551.6c15.1 37.3 66.6 40.4 86.1 5.3l90.6-163.1 196.3 196.3c7.8 7.8 18 11.7 28.2 11.7s20.4-3.9 28.2-11.7c15.5-15.5 15.5-40.7-0.1-56.3z" fill="#515151" p-id="12843"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703837617436" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1516" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M191.996412 319.999467m59.736381-59.736381l8.527708-8.527707q59.736381-59.736381 119.472761 0l392.529117 392.529116q59.736381 59.736381 0 119.472762l-8.527708 8.527708q-59.736381 59.736381-119.472762 0l-392.529116-392.529117q-59.736381-59.736381 0-119.472762Z" fill="#515151" p-id="1517"></path><path d="M191.996412 704.000876m59.736381-59.736381l392.529116-392.529116q59.736381-59.736381 119.472762 0l8.527708 8.527707q59.736381 59.736381 0 119.472762l-392.529117 392.529117q-59.736381 59.736381-119.472761 0l-8.527708-8.527708q-59.736381-59.736381 0-119.472762Z" fill="#515151" p-id="1518"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#515151" d="M512 960c-247.039484 0-448-200.960516-448-448S264.960516 64 512 64 960 264.960516 960 512 759.039484 960 512 960zM512 128c-211.744443 0-384 172.255557-384 384s172.255557 384 384 384 384-172.255557 384-384S723.744443 128 512 128z" /></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703837610361" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1310" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M341.09 469.09l72.78 72.78a48.27 48.27 0 0 0 68.26 0L743.4 280.6a48.26 48.26 0 0 1 55.71-9l37 18.5a48.26 48.26 0 0 1 12.54 77.29L478.54 737.46a48.27 48.27 0 0 1-64.27 3.54l-217-173.6c-29.86-23.9-21.4-71.38 14.89-83.47l79.54-26.52a48.26 48.26 0 0 1 49.39 11.68z" fill="#515151" p-id="1311"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#515151" d="M884.731 698.366H772.909V288.365c0-20.584-16.685-37.275-37.269-37.275H325.639V139.274c0-20.582-16.693-37.273-37.275-37.273-20.582 0-37.275 16.691-37.275 37.273V251.09H139.275c-20.584 0-37.275 16.691-37.275 37.275 0 20.582 16.691 37.273 37.275 37.273H251.09v410.003c0 20.582 16.693 37.269 37.275 37.269h410.003v111.82c0 20.583 16.685 37.269 37.267 37.269 20.589 0 37.275-16.685 37.275-37.269V772.91h111.822c20.582 0 37.269-16.687 37.269-37.269-0.001-20.588-16.688-37.275-37.27-37.275z m-559.092 0V325.638h372.728v372.728H325.639z m0 0" /></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850868080" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6322" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M170.666667 768m42.666666 0l597.333334 0q42.666667 0 42.666666 42.666667l0 0q0 42.666667-42.666666 42.666666l-597.333334 0q-42.666667 0-42.666666-42.666666l0 0q0-42.666667 42.666666-42.666667Z" fill="#515151" p-id="6323"></path><path d="M170.666667 853.333333m0-42.666666l0-85.333334q0-42.666667 42.666666-42.666666l0 0q42.666667 0 42.666667 42.666666l0 85.333334q0 42.666667-42.666667 42.666666l0 0q-42.666667 0-42.666666-42.666666Z" fill="#515151" p-id="6324"></path><path d="M768 853.333333m0-42.666666l0-85.333334q0-42.666667 42.666667-42.666666l0 0q42.666667 0 42.666666 42.666666l0 85.333334q0 42.666667-42.666666 42.666666l0 0q-42.666667 0-42.666667-42.666666Z" fill="#515151" p-id="6325"></path><path d="M512 640a42.666667 42.666667 0 0 1-24.746667-7.68l-170.666666-120.32a42.666667 42.666667 0 0 1-10.24-59.306667 42.666667 42.666667 0 0 1 59.733333-10.24L512 544.426667l145.066667-109.226667a42.666667 42.666667 0 0 1 51.2 68.266667l-170.666667 128a42.666667 42.666667 0 0 1-25.6 8.533333z" fill="#515151" p-id="6326"></path><path d="M512 554.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V170.666667a42.666667 42.666667 0 0 1 85.333334 0v341.333333a42.666667 42.666667 0 0 1-42.666667 42.666667z" fill="#515151" p-id="6327"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850318596" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16905" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M819.2 102.4h-56.53504c-18.76992 0-45.0048 10.87488-58.2656 24.13568l-51.2 51.2L629.07392 153.6c-26.5728-26.53184-70.00064-26.53184-96.57344 0L266.99776 419.10272a34.16064 34.16064 0 0 0 0 48.27136l12.07296 12.07296 289.62816-289.6384 36.22912 36.23936-362.02496 362.02496-0.03072-0.03072-92.17024 92.23168c-26.5728 26.5216-48.30208 78.97088-48.27136 116.50048L102.4 921.6h124.8256c37.49888 0 89.96864-21.72928 116.50048-48.26112l92.24192-92.20096 461.49632-461.49632C910.72512 306.37056 921.6 280.13568 921.6 261.36576V204.8L819.2 102.4zM295.46496 825.06752c-13.74208 13.73184-48.80384 28.30336-68.23936 28.30336l-56.53504-0.03072V796.7744c-0.03072-19.42528 14.49984-54.46656 28.27264-68.22912l43.9296-43.93984 96.53248 96.54272-43.96032 43.91936z m92.23168-92.20096l-96.53248-96.53248 362.02496-362.0352 96.57344 96.57344-362.06592 361.99424z m410.33728-410.2656l-96.5632-96.60416 72.3968-72.3968 96.60416 96.60416-72.43776 72.3968z" p-id="16906" fill="#515151"></path></svg>
\ No newline at end of file
<svg t="1731509655874" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4494" width="200" height="200"><path d="M99.5 284.8h61.3v-74l260.3 260.3 43.4-43.4L198.8 162h84.8v-61.4H99.5zM869.1 825.8L603.4 560 560 603.4l260.3 260.4h-73.9v61.3h184.1V741h-61.4zM160.1 742.4H98.7v184.1h184.2v-61.3h-68.6l263.1-263.1-43.4-43.4-273.9 273.9zM742 102v61.3h90.2l-263 263.1 43.4 43.4 252.2-252.2v68.5h61.4V102z" fill="#333333" p-id="4495"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702545991424" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4272" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M640 896h85.333333v-85.333333h-85.333333v85.333333z m170.666667-512h85.333333v-85.333333h-85.333333v85.333333zM128 213.333333v597.333334c0 47.146667 38.186667 85.333333 85.333333 85.333333h170.666667v-85.333333h-170.666667V213.333333h170.666667V128h-170.666667c-47.146667 0-85.333333 38.186667-85.333333 85.333333z m682.666667-85.333333v85.333333h85.333333c0-47.146667-38.186667-85.333333-85.333333-85.333333zM469.333333 981.333333h85.333334V42.666667h-85.333334v938.666666z m341.333334-256h85.333333v-85.333333h-85.333333v85.333333z m-170.666667-512h85.333333V128h-85.333333v85.333333z m170.666667 341.333334h85.333333v-85.333334h-85.333333v85.333334z m0 341.333333c47.146667 0 85.333333-38.186667 85.333333-85.333333h-85.333333v85.333333z" p-id="4273" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694849855144" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9603" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M210.773333 522.24a42.666667 42.666667 0 0 0-51.626666 32.426667l-20.906667 82.773333A42.666667 42.666667 0 0 0 170.666667 687.786667a42.666667 42.666667 0 0 0 51.626666-31.146667l20.906667-82.773333a42.666667 42.666667 0 0 0-32.426667-51.626667z m725.333334 320.853333l-170.666667-682.666666A42.666667 42.666667 0 0 0 725.333333 128h-128a42.666667 42.666667 0 0 0-42.666666 42.666667v682.666666a42.666667 42.666667 0 0 0 42.666666 42.666667h298.666667a42.666667 42.666667 0 0 0 33.706667-16.213333 42.666667 42.666667 0 0 0 8.96-36.693334zM640 810.666667V213.333333h52.053333l149.333334 597.333334zM273.066667 273.92a42.666667 42.666667 0 0 0-52.053334 31.146667L200.533333 387.84a42.666667 42.666667 0 0 0 31.146667 51.626667h10.24a42.666667 42.666667 0 0 0 42.666667-32.426667l20.48-82.773333a42.666667 42.666667 0 0 0-32-50.346667zM320.426667 213.333333h85.333333a42.666667 42.666667 0 0 0 0-85.333333h-85.333333a42.666667 42.666667 0 0 0 0 85.333333zM180.906667 810.666667a42.666667 42.666667 0 0 0-85.333334-10.24l-10.24 42.666666a42.666667 42.666667 0 0 0 7.68 36.693334A42.666667 42.666667 0 0 0 128 896h42.666667a42.666667 42.666667 0 0 0 10.24-85.333333zM426.666667 277.76a42.666667 42.666667 0 0 0-42.666667 42.666667v85.333333a42.666667 42.666667 0 0 0 85.333333 0v-85.333333a42.666667 42.666667 0 0 0-42.666666-42.666667z m0 256a42.666667 42.666667 0 0 0-42.666667 42.666667v85.333333a42.666667 42.666667 0 0 0 85.333333 0v-85.333333a42.666667 42.666667 0 0 0-42.666666-42.666667z m0 256a42.666667 42.666667 0 0 0-36.693334 20.906667H341.333333a42.666667 42.666667 0 0 0 0 85.333333h85.333334a42.666667 42.666667 0 0 0 42.666666-42.666667v-20.906666a42.666667 42.666667 0 0 0-42.666666-42.666667z" p-id="9604" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694849870986" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9778" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M563.626667 243.2a42.666667 42.666667 0 0 0 10.24 0l82.773333-20.906667A42.666667 42.666667 0 0 0 687.786667 170.666667a42.666667 42.666667 0 0 0-51.626667-31.146667L554.666667 159.146667a42.666667 42.666667 0 0 0 10.24 85.333333zM405.76 384h-85.333333a42.666667 42.666667 0 1 0 0 85.333333h85.333333a42.666667 42.666667 0 0 0 0-85.333333z m170.666667 0a42.666667 42.666667 0 1 0 0 85.333333h85.333333a42.666667 42.666667 0 0 0 0-85.333333zM315.306667 305.066667h10.24l82.773333-20.48a42.666667 42.666667 0 0 0-20.48-84.053334l-82.773333 20.48a42.666667 42.666667 0 0 0 10.24 85.333334zM170.666667 448.426667a42.666667 42.666667 0 0 0 42.666666-42.666667v-85.333333a42.666667 42.666667 0 0 0-85.333333 0v85.333333a42.666667 42.666667 0 0 0 42.666667 42.666667z m709.12-354.133334a42.666667 42.666667 0 0 0-36.693334-8.96l-42.666666 10.24a42.666667 42.666667 0 0 0-31.146667 51.626667 42.666667 42.666667 0 0 0 42.666667 32.426667A42.666667 42.666667 0 0 0 896 170.666667V128a42.666667 42.666667 0 0 0-16.213333-33.706667zM853.333333 298.666667a42.666667 42.666667 0 0 0-42.666666 42.666666v48.64a42.666667 42.666667 0 0 0 21.76 79.36H853.333333a42.666667 42.666667 0 0 0 42.666667-42.666666V341.333333a42.666667 42.666667 0 0 0-42.666667-42.666666z m0 256H170.666667a42.666667 42.666667 0 0 0-42.666667 42.666666v128a42.666667 42.666667 0 0 0 32.426667 42.666667l682.666666 170.666667a42.666667 42.666667 0 0 0 10.24 0 42.666667 42.666667 0 0 0 42.666667-42.666667v-298.666667a42.666667 42.666667 0 0 0-42.666667-42.666666z m-42.666666 286.72l-597.333334-149.333334V640h597.333334z" p-id="9779" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694854421840" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="28174" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M870.4 204.8c-18.6368 0-36.1472 5.0176-51.2 13.7728l0-64.9728c0-56.4736-45.9264-102.4-102.4-102.4-21.0944 0-40.6528 6.4-56.9856 17.3568-14.0288-39.8848-52.0192-68.5568-96.6144-68.5568s-82.6368 28.672-96.6144 68.5568c-16.2816-10.9568-35.8912-17.3568-56.9856-17.3568-56.4736 0-102.4 45.9264-102.4 102.4l0 377.4976-68.9152-119.4496c-13.3632-24.32-35.1744-41.6256-61.3888-48.7936-25.5488-6.9632-52.1216-3.2768-74.8544 10.3424-46.4384 27.8528-64.1536 90.8288-39.424 140.3904 1.536 3.1232 34.2016 70.0416 136.192 273.92 48.0256 96 100.7104 164.6592 156.6208 203.9808 43.8784 30.8736 74.1888 32.4608 79.8208 32.4608l256 0c43.5712 0 84.0704-14.1824 120.4224-42.0864 34.1504-26.2656 63.7952-64.256 88.064-112.8448 47.8208-95.6416 73.1136-227.9424 73.1136-382.6688l0-179.2c0-56.4736-45.9264-102.4-102.4-102.4zM921.6 486.4c0 146.7904-23.3984 271.1552-67.6864 359.7312-28.8768 57.7536-80.5888 126.6688-162.7136 126.6688l-255.488 0c-1.9968-0.1536-23.552-2.56-56.064-26.88-32.4096-24.2688-82.176-75.3664-135.0656-181.248-103.7824-207.5648-135.68-272.9472-135.9872-273.5616-0.0512-0.1024-0.0512-0.1536-0.1024-0.2048-12.8512-25.7536-3.7376-59.4944 19.9168-73.6768 10.6496-6.4 23.0912-8.0896 35.072-4.864 12.7488 3.4816 23.4496 12.0832 30.0544 24.1664 0.1024 0.1536 0.2048 0.3584 0.3072 0.512l79.9232 138.496c16.3328 29.8496 34.7136 42.3936 54.6304 37.3248 19.968-5.0688 30.0544-25.0368 30.0544-59.2384l0-400.0256c0-28.2112 22.9888-51.2 51.2-51.2s51.2 22.9888 51.2 51.2l0 332.8c0 14.1312 11.4688 25.6 25.6 25.6s25.6-11.4688 25.6-25.6l0-384c0-28.2112 22.9888-51.2 51.2-51.2s51.2 22.9888 51.2 51.2l0 384c0 14.1312 11.4688 25.6 25.6 25.6s25.6-11.4688 25.6-25.6l0-332.8c0-28.2112 22.9888-51.2 51.2-51.2s51.2 22.9888 51.2 51.2l0 384c0 14.1312 11.4688 25.6 25.6 25.6s25.6-11.4688 25.6-25.6l0-230.4c0-28.2112 22.9888-51.2 51.2-51.2s51.2 22.9888 51.2 51.2l0 179.2z" fill="#515151" p-id="28175"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702973104380" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4243" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M533.333333 85.333333C384 85.333333 251.733333 166.4 183.466667 290.133333L85.333333 192 85.333333 469.333333l277.333333 0L243.2 349.866667C298.666667 243.2 405.333333 170.666667 533.333333 170.666667c174.933333 0 320 145.066667 320 320 0 174.933333-145.066667 320-320 320-140.8 0-256-89.6-302.933333-213.333333L140.8 597.333333c46.933333 170.666667 204.8 298.666667 392.533333 298.666667 226.133333 0 405.333333-183.466667 405.333333-405.333333S755.2 85.333333 533.333333 85.333333zM469.333333 298.666667l0 217.6 200.533333 119.466667 34.133333-55.466667-170.666667-102.4L533.333333 298.666667 469.333333 298.666667z" opacity="0.9" p-id="4244" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#515151" d="M512 212h300v300H512zM212 512h300v300H212z" /><path fill="#515151" d="M812 992H212a180 180 0 0 1-180-180V212a180 180 0 0 1 180-180h600a180 180 0 0 1 180 180v600a180 180 0 0 1-180 180zM212 152a60 60 0 0 0-60 60v600a60 60 0 0 0 60 60h600a60 60 0 0 0 60-60V212a60 60 0 0 0-60-60z" /></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702438231396" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2364" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M85.344 106.656H256v170.656H85.344V106.656zM85.344 448H256v170.656H85.344V448zM256 277.344h170.656V448H256V277.344zM426.656 106.656h170.656v170.656h-170.656V106.656zM426.656 448h170.656v170.656h-170.656V448zM597.344 277.344H768V448h-170.656V277.344zM597.344 768h-170.656v-149.344H256.032V768H85.376v170.656h170.656v-149.344h170.656v149.344h170.656v-149.344H768v149.344h170.656V768H768v-149.344h-170.656zM768 106.656h170.656v170.656H768V106.656zM768 448h170.656v170.656H768V448z" p-id="2365" fill="#515151" data-spm-anchor-id="a313x.search_index.0.i1.70153a81pH8VDw" class=""></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1701141069715" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5133" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M841.34 959.36H182.66c-65.06 0-117.99-52.94-117.99-118.02V182.69c0-65.08 52.94-118.04 117.99-118.04h658.68c65.06 0 117.99 52.96 117.99 118.04v658.65c0 65.08-52.93 118.02-117.99 118.02zM182.66 142.17c-22.31 0-40.51 18.18-40.51 40.51v658.65c0 22.34 18.2 40.49 40.51 40.49h658.68c22.31 0 40.51-18.15 40.51-40.49V182.69c0-22.34-18.2-40.51-40.51-40.51H182.66z" fill="#515151" p-id="5134"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850987960" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2591" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M765.013333 509.013333C487.253333 235.392 67.968 391.466667 65.792 697.941333l7.765333 2.645334c176.853333-218.112 417.024-291.84 605.866667-106.453334 1.706667 1.706667 1.792 4.608 0.085333 6.314667L561.92 718.08a4.266667 4.266667 0 0 0 2.986667 7.296h326.826666a4.266667 4.266667 0 0 0 4.266667-4.266667V394.24a4.224 4.224 0 0 0-7.253333-2.986667l-117.76 117.76a4.096 4.096 0 0 1-5.888 0z" p-id="2592" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1702708510489" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5331" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M831.167365 607.851819c-15.128556-6.096582-32.063506 1.354796-38.160088 16.483352-46.288864 115.609261-156.478942 190.348842-281.120177 190.348842-104.770893 0-199.606615-52.837045-254.475854-138.640794l193.735832 39.514885c15.805954 3.161191 31.386108-6.999779 34.547299-22.805734 3.161191-15.805954-6.999779-31.386108-22.805733-34.547298l-252.895259-51.933848h-0.903198c-0.677398-0.225799-1.580595-0.225799-2.257993-0.2258H202.993605c-0.677398 0-1.354796 0-2.032194 0.2258-0.451599 0-0.677398 0-1.128996 0.225799-0.677398 0-1.128997 0.225799-1.806395 0.225799-0.451599 0-0.903197 0.225799-1.128997 0.2258-0.451599 0.225799-1.128997 0.225799-1.580595 0.451599-0.451599 0.225799-0.903197 0.225799-1.354796 0.451598-0.225799 0.225799-0.677398 0.225799-0.903198 0.2258-0.225799 0-0.225799 0.225799-0.451598 0.225799l-1.354796 0.677398-1.354796 0.677398c-0.451599 0.225799-0.677398 0.451599-1.128997 0.677398-0.451599 0.225799-1.128997 0.677398-1.580595 0.903197-0.225799 0-0.225799 0.225799-0.451599 0.2258-0.225799 0.225799-0.451599 0.225799-0.451599 0.451598-0.451599 0.451599-0.903197 0.677398-1.580595 1.128997-0.225799 0.225799-0.451599 0.451599-0.903197 0.677398l-1.354797 1.354796c-0.225799 0.225799-0.451599 0.451599-0.677398 0.903197-0.451599 0.451599-0.677398 0.903197-1.128996 1.354796-0.225799 0.225799-0.451599 0.677398-0.677398 0.903198-0.225799 0.451599-0.677398 0.903197-0.903198 1.354796-0.225799 0.451599-0.451599 0.677398-0.677398 1.128996l-0.677398 1.354797c-0.225799 0.451599-0.451599 0.677398-0.451598 1.128996-0.225799 0.451599-0.451599 0.903197-0.677398 1.580596-0.225799 0.451599-0.225799 0.677398-0.451599 1.128996-0.225799 0.677398-0.451599 1.128997-0.451599 1.806395 0 0.451599-0.225799 0.677398-0.225799 1.128997-0.225799 0.677398-0.225799 1.354796-0.451599 2.257993v1.128997L141.350386 887.842999c-2.032194 16.031753 9.257773 30.70871 25.289526 32.966703 1.354796 0.225799 2.483793 0.225799 3.838589 0.2258 14.451158 0 27.095921-10.838368 29.128114-25.515326l21.676737-168.672105c23.483131 31.837707 52.385447 59.836825 85.577949 82.868357 60.514223 41.772878 131.189416 63.675413 205.025799 63.675413s144.511577-22.128335 205.0258-63.675413c58.933627-40.643881 104.093495-97.319515 130.737817-163.704521 5.870783-15.128556-1.354796-32.289305-16.483352-38.160088zM857.134289 103.190298c-16.031753-2.032194-30.70871 9.257773-32.966704 25.289526l-21.676736 168.672105c-23.483131-31.837707-52.385447-59.836825-85.577949-82.642557-60.514223-41.772878-131.189416-63.675413-205.0258-63.675414-73.610584 0-144.511577 22.128335-205.025799 63.675414-58.933627 40.643881-104.093495 97.319515-130.737817 163.70452-6.096582 15.128556 1.354796 32.063506 16.483352 38.160088 15.128556 6.096582 32.063506-1.354796 38.160088-16.483352 46.288864-115.609261 156.478942-190.348842 281.120176-190.348842 104.770893 0 199.606615 52.837045 254.475855 138.640794L572.627122 308.441896c-15.805954-3.161191-31.386108 6.999779-34.547298 22.805734-3.161191 15.805954 6.999779 31.386108 22.805733 34.547298l253.572657 51.708049h0.451599c0.677398 0.225799 1.580595 0.225799 2.257993 0.451599h0.451599c0.903197 0 1.806395 0.225799 2.709592 0.225799h2.257993c0.451599 0 0.903197 0 1.128997-0.225799 0.451599 0 0.903197-0.225799 1.354796-0.2258 0.451599 0 0.677398-0.225799 1.128996-0.225799s0.903197-0.225799 1.580596-0.451599c0.225799 0 0.677398-0.225799 0.903197-0.225799 0.677398-0.225799 1.354796-0.451599 1.806395-0.677398 0.225799 0 0.225799 0 0.451599-0.2258 0.903197-0.225799 1.580595-0.677398 2.483792-1.128996 0.225799-0.225799 0.451599-0.225799 0.903198-0.451599s0.903197-0.451599 1.354796-0.903197c0.225799-0.225799 0.677398-0.451599 0.903197-0.677398 0.225799 0 0.225799-0.225799 0.451599-0.2258l0.451598-0.451598c0.451599-0.451599 1.128997-0.677398 1.580596-1.128997l0.677398-0.677398 1.354796-1.354796c0.225799-0.225799 0.451599-0.451599 0.677398-0.903197 0.451599-0.451599 0.677398-0.903197 1.128997-1.354796 0.225799-0.225799 0.451599-0.677398 0.677398-0.903198 0.225799-0.451599 0.677398-0.903197 0.903197-1.354796 0.225799-0.451599 0.451599-0.677398 0.677398-1.128996l0.677398-1.354797c0.225799-0.451599 0.451599-0.903197 0.677398-1.128996l0.677398-1.354796c0.225799-0.451599 0.225799-0.903197 0.451599-1.354796 0.225799-0.451599 0.225799-1.128997 0.451598-1.580596 0-0.451599 0.225799-0.677398 0.2258-1.128996 0.225799-0.677398 0.225799-1.354796 0.451598-2.032194 0-0.225799 0-0.451599 0.2258-0.677398v-0.2258l32.966703-256.282249c2.709592-16.257552-8.806174-30.934509-24.837927-32.966703z" fill="#515151" p-id="5332" data-spm-anchor-id="a313x.search_index.0.i5.79943a813nilqH" class="selected"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850121784" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10964" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M480.5 251.2c13-1.6 25.9-2.4 38.8-2.5v63.9c0 6.5 7.5 10.1 12.6 6.1L660 217.6c4-3.2 4-9.2 0-12.3l-128-101c-5.1-4-12.6-0.4-12.6 6.1l-0.2 64c-118.6 0.5-235.8 53.4-314.6 154.2-69.6 89.2-95.7 198.6-81.1 302.4h74.9c-0.9-5.3-1.7-10.7-2.4-16.1-5.1-42.1-2.1-84.1 8.9-124.8 11.4-42.2 31-81.1 58.1-115.8 27.2-34.7 60.3-63.2 98.4-84.3 37-20.6 76.9-33.6 119.1-38.8z" p-id="10965" fill="#515151"></path><path d="M880 418H352c-17.7 0-32 14.3-32 32v414c0 17.7 14.3 32 32 32h528c17.7 0 32-14.3 32-32V450c0-17.7-14.3-32-32-32z m-44 402H396V494h440v326z" p-id="10966" fill="#515151"></path></svg>
\ No newline at end of file
<svg t="1731509731687" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5465" width="200" height="200"><path d="M672 352h160a32 32 0 0 1 0 64H640a32 32 0 0 1-32-32V192a32 32 0 0 1 64 0z" p-id="5466"></path><path d="M662.72 406.72a32 32 0 0 1-45.44-45.44l224-224a32 32 0 1 1 45.44 45.44zM352 352V192a32 32 0 0 1 64 0v192a32 32 0 0 1-32 32H192a32 32 0 0 1 0-64z" p-id="5467"></path><path d="M406.72 361.28a32 32 0 0 1-45.44 45.44l-224-224a32 32 0 0 1 45.44-45.44zM672 672v160a32 32 0 0 1-64 0V640a32 32 0 0 1 32-32h192a32 32 0 0 1 0 64z" p-id="5468"></path><path d="M617.28 662.72a32 32 0 0 1 45.44-45.44l224 224a32 32 0 0 1-45.44 45.44zM192 672a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V672z" p-id="5469"></path><path d="M361.28 617.28a32 32 0 0 1 45.44 45.44l-224 224a32 32 0 0 1-45.44-45.44z" p-id="5470"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1703559147194" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5088" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 96c229.76 0 416 186.24 416 416s-186.24 416-416 416S96 741.76 96 512 282.24 96 512 96z m196.266667 384H315.733333a17.066667 17.066667 0 0 0-17.066666 17.066667v29.866666c0 9.386667 7.68 17.066667 17.066666 17.066667h392.533334a17.066667 17.066667 0 0 0 17.066666-17.066667v-29.866666a17.066667 17.066667 0 0 0-17.066666-17.066667z" fill="#515151" fill-opacity=".87" p-id="5089"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694849832460" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8562" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M853.333333 170.666667H170.666667a42.666667 42.666667 0 0 0-42.666667 42.666666v128a42.666667 42.666667 0 0 0 85.333333 0V256h256v554.666667H384a42.666667 42.666667 0 0 0 0 85.333333h256a42.666667 42.666667 0 0 0 0-85.333333h-85.333333V256h256v85.333333a42.666667 42.666667 0 0 0 85.333333 0V213.333333a42.666667 42.666667 0 0 0-42.666667-42.666666z" p-id="8563" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#515151" d="M799.5 179c24.6 0 44.5 20 44.5 44.5v575.9c0 24.6-20 44.5-44.5 44.5h-576c-24.6 0-44.5-20-44.5-44.5V223.5c0-24.6 20-44.5 44.5-44.5h576m0-120h-576C132.7 59 59 132.7 59 223.5v575.9C59 890.3 132.7 964 223.5 964h575.9c90.9 0 164.5-73.7 164.5-164.5v-576C964 132.7 890.3 59 799.5 59z" /></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850976834" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7598" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M258.986667 509.013333c277.76-273.621333 697.045333-117.546667 699.306666 188.885334l-7.765333 2.645333c-176.853333-218.112-417.024-291.84-605.866667-106.453333a4.522667 4.522667 0 0 0-0.085333 6.314666l117.589333 117.589334a4.266667 4.266667 0 0 1-2.986666 7.253333H132.266667a4.266667 4.266667 0 0 1-4.266667-4.266667V394.24c0-3.84 4.608-5.717333 7.253333-2.986667l117.76 117.76c1.621333 1.706667 4.266667 1.621333 5.888 0z" p-id="7599" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694850896169" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6500" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M853.333333 256m-42.666666 0l-597.333334 0q-42.666667 0-42.666666-42.666667l0 0q0-42.666667 42.666666-42.666666l597.333334 0q42.666667 0 42.666666 42.666666l0 0q0 42.666667-42.666666 42.666667Z" fill="#515151" p-id="6501"></path><path d="M853.333333 170.666667m0 42.666666l0 85.333334q0 42.666667-42.666666 42.666666l0 0q-42.666667 0-42.666667-42.666666l0-85.333334q0-42.666667 42.666667-42.666666l0 0q42.666667 0 42.666666 42.666666Z" fill="#515151" p-id="6502"></path><path d="M256 170.666667m0 42.666666l0 85.333334q0 42.666667-42.666667 42.666666l0 0q-42.666667 0-42.666666-42.666666l0-85.333334q0-42.666667 42.666666-42.666666l0 0q42.666667 0 42.666667 42.666666Z" fill="#515151" p-id="6503"></path><path d="M341.333333 597.333333a42.666667 42.666667 0 0 1-34.133333-17.066666 42.666667 42.666667 0 0 1 8.533333-59.733334l170.666667-128a42.666667 42.666667 0 0 1 50.346667 0l170.666666 120.32a42.666667 42.666667 0 0 1 10.24 59.306667 42.666667 42.666667 0 0 1-59.733333 10.24L512 479.573333 366.933333 588.8a42.666667 42.666667 0 0 1-25.6 8.533333z" fill="#515151" p-id="6504"></path><path d="M512 896a42.666667 42.666667 0 0 1-42.666667-42.666667v-341.333333a42.666667 42.666667 0 0 1 85.333334 0v341.333333a42.666667 42.666667 0 0 1-42.666667 42.666667z" fill="#515151" p-id="6505"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1695106630007" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6574" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M637 443H519V309c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v134H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h118v134c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V519h118c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z" p-id="6575" fill="#515151"></path><path d="M921 867L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z" p-id="6576" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1695106635608" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6749" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M637 443H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h312c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z" p-id="6750" fill="#515151"></path><path d="M921 867L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z" p-id="6751" fill="#515151"></path></svg>
\ No newline at end of file
此差异已折叠。
import { Canvas, Ellipse, FabricObject, Point, TDegree } from "fabric";
import ImageEditor from "./image_editor";
interface OperationAction {
undo(): void;
redo(): void;
}
enum State {
CAN_UNDO, CAN_REDO
}
class CreateAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected state: State = State.CAN_UNDO;
constructor(canvas: Canvas, object: FabricObject) {
this.canvas = canvas;
this.object = object;
}
undo() {
if (this.state == State.CAN_UNDO) {
this.canvas.remove(this.object);
this.canvas.renderAll();
this.state = State.CAN_REDO;
}
}
redo() {
if (this.state == State.CAN_REDO) {
this.canvas.add(this.object);
this.canvas.renderAll();
this.state = State.CAN_UNDO;
}
}
}
class RemoveAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected state: State = State.CAN_UNDO;
constructor(canvas: Canvas, object: FabricObject) {
this.canvas = canvas;
this.object = object;
}
undo() {
if (this.state == State.CAN_UNDO) {
this.canvas.add(this.object);
this.canvas.renderAll();
this.state = State.CAN_REDO;
}
}
redo() {
if (this.state == State.CAN_REDO) {
this.canvas.remove(this.object);
this.canvas.renderAll();
this.state = State.CAN_UNDO;
}
}
}
class MoveAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected previousX: number;
protected previousY: number;
protected currentX: number;
protected currentY: number;
constructor(canvas: Canvas, object: FabricObject, previousX: number, previousY: number) {
this.object = object;
this.previousX = previousX;
this.previousY = previousY;
this.currentX = object.getX();
this.currentY = object.getY();
this.canvas = canvas;
}
undo(): void {
this.object.setX(this.previousX);
this.object.setY(this.previousY);
this.canvas.renderAll();
}
redo(): void {
this.object.setX(this.currentX);
this.object.setY(this.currentY);
this.canvas.renderAll();
}
}
class ScaleAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected previousWidth: number;
protected previousHeight: number;
protected previousX: number;
protected previousY: number;
protected currentWidth: number;
protected currentHeight: number;
protected currentX: number;
protected currentY: number;
constructor(canvas: Canvas, object: FabricObject, previousWidth: number, previousHeight: number, previousX: number, previousY: number) {
this.canvas = canvas;
this.object = object;
this.previousWidth = previousWidth;
this.previousHeight = previousHeight;
this.previousX = previousX;
this.previousY = previousY;
this.currentWidth = object.get('width');
this.currentHeight = object.get('height');
this.currentX = object.getX();
this.currentY = object.getY();
}
undo(): void {
this.object.set({ width: this.previousWidth, height: this.previousHeight });
this.object.setX(this.previousX);
this.object.setY(this.previousY);
this.canvas.renderAll();
}
redo(): void {
this.object.set({ width: this.currentWidth, height: this.currentHeight });
this.object.setX(this.currentX);
this.object.setY(this.currentY);
this.canvas.renderAll();
}
}
class EllipseScaleAction extends ScaleAction {
protected previousRX: number;
protected previousRY: number;
protected currentRX: number;
protected currentRY: number;
constructor(canvas: Canvas, object: Ellipse, previousWidth: number, previousHeight: number
, previousX: number, previousY: number, previousRX: number, previousRY: number) {
super(canvas, object, previousWidth, previousHeight, previousX, previousY);
this.previousRX = previousRX;
this.previousRY = previousRY;
console.log(this.previousRX)
console.log(this.previousRY)
this.currentRX = object.rx;
this.currentRY = object.ry;
}
undo(): void {
const obj = this.object as Ellipse;
obj.rx = this.previousRX;
obj.ry = this.previousRY;
super.undo();
}
redo(): void {
const obj = this.object as Ellipse;
obj.rx = this.currentRX;
obj.ry = this.currentRY;
super.redo();
}
}
class RatioScaleAction implements OperationAction {
protected canvas: Canvas;
protected object: FabricObject;
protected previousScaleX: number;
protected previousScaleY: number;
protected previousX: number;
protected previousY: number;
protected currentScaleX: number;
protected currentScaleY: number;
protected currentX: number;
protected currentY: number;
constructor(canvas: Canvas, object: FabricObject, previousScaleX: number, previousScaleY: number, previousX: number, previousY: number) {
this.canvas = canvas;
this.object = object;
this.previousScaleX = previousScaleX;
this.previousScaleY = previousScaleY;
this.previousX = previousX;
this.previousY = previousY;
this.currentScaleX = object.scaleX;
this.currentScaleY = object.scaleY;
this.currentX = object.getX();
this.currentY = object.getY();
}
undo(): void {
this.object.scaleX = this.previousScaleX;
this.object.scaleY = this.previousScaleY;
this.object.setX(this.previousX);
this.object.setY(this.previousY);
this.canvas.renderAll();
}
redo(): void {
this.object.scaleX = this.currentScaleX;
this.object.scaleY = this.currentScaleY;
this.object.setX(this.currentX);
this.object.setY(this.currentY);
this.canvas.renderAll();
}
}
export class FlipXUndoProps {
fabricWrapperEl?: HTMLDivElement;
left: string = '';
backgroundImage?: FabricObject;
flipX: boolean = false;
x: number = 0;
objs: FabricObject[] = [];
objX: number[] = [];
objFlipX: boolean[] = [];
}
class FlipXAction implements OperationAction {
private canvas: Canvas;
private previous: FlipXUndoProps;
private current: FlipXUndoProps;
private hasUndo = false;
constructor(canvas: Canvas, previous: FlipXUndoProps, current: FlipXUndoProps) {
this.canvas = canvas;
this.previous = previous;
this.current = current;
}
undo(): void {
if (this.hasUndo) {
return;
}
const previous = this.previous;
const bi = previous.backgroundImage;
const fabricWrapperEl = previous.fabricWrapperEl;
fabricWrapperEl!.style.left = previous.left;
bi!.flipX = previous.flipX;
bi!.setX(previous.x);
for (const index in previous.objs) {
const obj = previous.objs[index];
const flipX = previous.objFlipX[index];
const x = previous.objX[index];
obj.flipX = flipX;
obj.setX(x);
obj.setCoords();
}
this.canvas.renderAll();
this.hasUndo = true;
}
redo(): void {
if (!this.hasUndo) {
return;
}
const current = this.current;
const bi = current.backgroundImage;
const fabricWrapperEl = current.fabricWrapperEl;
fabricWrapperEl!.style.left = current.left;
bi!.flipX = current.flipX;
bi!.setX(current.x);
for (const index in current.objs) {
const obj = current.objs[index];
const flipX = current.objFlipX[index];
const x = current.objX[index];
obj.flipX = flipX;
obj.setX(x);
obj.setCoords();
}
this.canvas.renderAll();
this.hasUndo = false
}
}
export class FlipYUndoProps {
fabricWrapperEl?: HTMLDivElement;
top: string = '';
backgroundImage?: FabricObject;
flipY: boolean = false;
y: number = 0;
objs: FabricObject[] = [];
objY: number[] = [];
objFlipY: boolean[] = [];
}
class FlipYAction implements OperationAction {
private canvas: Canvas;
private previous: FlipYUndoProps;
private current: FlipYUndoProps;
private hasUndo = false;
constructor(canvas: Canvas, previous: FlipYUndoProps, current: FlipYUndoProps) {
this.canvas = canvas;
this.previous = previous;
this.current = current;
}
undo(): void {
if (this.hasUndo) {
return;
}
const previous = this.previous;
const bi = previous.backgroundImage;
const fabricWrapperEl = previous.fabricWrapperEl;
fabricWrapperEl!.style.top = previous.top;
bi!.flipY = previous.flipY;
bi!.setY(previous.y);
for (const index in previous.objs) {
const obj = previous.objs[index];
const flipY = previous.objFlipY[index];
const y = previous.objY[index];
obj.flipY = flipY;
obj.setY(y);
obj.setCoords();
}
this.canvas.renderAll();
this.hasUndo = true;
}
redo(): void {
if (!this.hasUndo) {
return;
}
const current = this.current;
const bi = current.backgroundImage;
const fabricWrapperEl = current.fabricWrapperEl;
fabricWrapperEl!.style.top = current.top;
bi!.flipY = current.flipY;
bi!.setY(current.y);
for (const index in current.objs) {
const obj = current.objs[index];
const flipY = current.objFlipY[index];
const y = current.objY[index];
obj.flipY = flipY;
obj.setY(y);
obj.setCoords();
}
this.canvas.renderAll();
this.hasUndo = false
}
}
export class CanvasWrapperProps {
canvasWrapper?: HTMLDivElement;
canvasWrapperLeft = '';
canvasWrapperTop = '';
topResizer?: HTMLDivElement;
topResizerLeft = '';
topResizerTop = '';
leftResizer?: HTMLDivElement;
leftResizerLeft = '';
leftResizerTop = '';
bottomResizer?: HTMLDivElement;
bottomResizerTop = '';
bottomResizerLeft = '';
rightResizer?: HTMLDivElement;
rightResizerTop = '';
rightResizerLeft = '';
toolbar?: HTMLDivElement;
toolbarLeft = '';
toolbarTop = '';
optionBar?: HTMLDivElement;
optionBarLeft = '';
optionBarTop = '';
}
export class CanvasDetailedProps extends CanvasWrapperProps {
canvasWrapperHeight = ''
canvasWrapperWidth = '';
}
export class FabricCanvasProps {
fabricWrapperEl?: HTMLDivElement;
fabricWrapperElLeft = '';
fabricWrapperElTop = '';
canvasWidth = 0;
canvasHeight = 0;
canvasBackgroundColor = '';
canvasBackgroundImage?: FabricObject
objects = [] as FabricObject[]
}
export class RotateProps {
fabricWrapperEl?: HTMLDivElement;
canvasWrapper?: HTMLDivElement;
imageEditor?: ImageEditor;
left: string = '';
top: string = '';
canvasHeight: number = 0;
canvasWidth: number = 0;
height: string = '';
width: string = '';
objs: FabricObject[] = [];
objPos: any[] = [];
objAngle: TDegree[] = [];
canvasWrapperProps?: CanvasWrapperProps;
}
abstract class CanvasSetAction implements OperationAction {
undo(): void {
}
redo(): void {
}
resetCanvasWrapper(prop: CanvasWrapperProps) {
prop.canvasWrapper!.style.top = prop.canvasWrapperTop;
prop.canvasWrapper!.style.left = prop.canvasWrapperLeft;
prop.topResizer!.style.top = prop.topResizerTop;
prop.topResizer!.style.left = prop.topResizerLeft;
prop.leftResizer!.style.top = prop.leftResizerTop;
prop.leftResizer!.style.left = prop.leftResizerLeft;
prop.bottomResizer!.style.top = prop.bottomResizerTop;
prop.bottomResizer!.style.left = prop.bottomResizerLeft;
prop.rightResizer!.style.top = prop.rightResizerTop;
prop.rightResizer!.style.left = prop.rightResizerLeft;
prop.toolbar!.style.top = prop.toolbarTop;
prop.toolbar!.style.left = prop.toolbarLeft;
prop.optionBar!.style.top = prop.optionBarTop;
prop.optionBar!.style.left = prop.optionBarLeft;
}
}
class RotationAction extends CanvasSetAction {
private previous: RotateProps;
private current: RotateProps;
private canvas: Canvas;
private hasUndo = false;
constructor(canvas: Canvas, previous: RotateProps, current: RotateProps) {
super();
this.canvas = canvas;
this.current = current;
this.previous = previous;
}
undo(): void {
if (this.hasUndo) {
return;
}
const previous = this.previous;
previous.imageEditor!.setCanvasDims(previous.canvasWidth, previous.canvasHeight);
const fwStyle = previous.fabricWrapperEl!.style;
fwStyle.left = previous.left;
fwStyle.top = previous.top;
const canvasWrapper = previous.canvasWrapper!;
canvasWrapper.style.width = previous.width;
canvasWrapper.style.height = previous.height;
for (const index in previous.objs) {
const obj = previous.objs[index];
const xy = previous.objPos[index];
const angle = previous.objAngle[index];
obj.set('angle', angle)
obj.setXY(new Point(xy.x, xy.y));
obj.setCoords();
}
this.canvas.renderAll();
this.resetCanvasWrapper(previous.canvasWrapperProps!);
this.hasUndo = true;
}
redo(): void {
if (!this.hasUndo) {
return;
}
const current = this.current;
current.imageEditor!.setCanvasDims(current.canvasWidth, current.canvasHeight);
const fwStyle = current.fabricWrapperEl!.style;
fwStyle.left = current.left;
fwStyle.top = current.top;
const canvasWrapper = current.canvasWrapper!;
canvasWrapper.style.width = current.width;
canvasWrapper.style.height = current.height;
for (const index in current.objs) {
const obj = current.objs[index];
const xy = current.objPos[index];
const angle = current.objAngle[index];
obj.set('angle', angle)
obj.setXY(new Point(xy.x, xy.y));
obj.setCoords();
}
this.canvas.renderAll();
this.resetCanvasWrapper(current.canvasWrapperProps!);
this.hasUndo = false;
}
}
class CropAction extends CanvasSetAction {
private previousWrapper: CanvasDetailedProps;
private previousCanvas: FabricCanvasProps;
private cropWrapper: CanvasDetailedProps;
private cropCanvas: FabricCanvasProps;
private canvas: Canvas;
private hasUndo = false;
constructor(canvas: Canvas, previousWrapper: CanvasDetailedProps, previousCanvas: FabricCanvasProps
, cropWrapper: CanvasDetailedProps, cropCanvas: FabricCanvasProps
) {
super();
this.canvas = canvas;
this.previousWrapper = previousWrapper;
this.previousCanvas = previousCanvas;
this.cropWrapper = cropWrapper;
this.cropCanvas = cropCanvas;
}
undo(): void {
if (this.hasUndo) {
return;
}
this.clearCanvas();
super.resetCanvasWrapper(this.previousWrapper);
const pw = this.previousWrapper;
pw.canvasWrapper!.style.width = pw.canvasWrapperWidth;
pw.canvasWrapper!.style.height = pw.canvasWrapperHeight;
const pc = this.previousCanvas;
pc.fabricWrapperEl!.style.left = pc.fabricWrapperElLeft;
pc.fabricWrapperEl!.style.top = pc.fabricWrapperElTop;
this.canvas.setDimensions({ width: pc.canvasWidth, height: pc.canvasHeight });
this.canvas.backgroundColor = pc.canvasBackgroundColor;
this.canvas.backgroundImage = pc.canvasBackgroundImage!;
for (const object of pc.objects) {
this.canvas.add(object);
object.setCoords();
}
this.canvas.renderAll();
this.hasUndo = true;
}
redo(): void {
if (!this.hasUndo) {
return;
}
this.clearCanvas();
super.resetCanvasWrapper(this.cropWrapper);
const cw = this.cropWrapper;
cw.canvasWrapper!.style.width = cw.canvasWrapperWidth;
cw.canvasWrapper!.style.height = cw.canvasWrapperHeight;
const cc = this.cropCanvas;
cc.fabricWrapperEl!.style.left = cc.fabricWrapperElLeft;
cc.fabricWrapperEl!.style.top = cc.fabricWrapperElTop;
this.canvas.setDimensions({ width: cc.canvasWidth, height: cc.canvasHeight });
this.canvas.backgroundColor = cc.canvasBackgroundColor;
this.canvas.backgroundImage = cc.canvasBackgroundImage!;
for (const object of cc.objects) {
this.canvas.add(object);
object.setCoords();
}
this.canvas.renderAll();
this.hasUndo = false;
}
clearCanvas() {
// 不太清楚fabric会不会destory这些对象,所以防止万一起见,还是先删除了
const canvas = this.canvas;
canvas.backgroundImage = undefined;
const objects = canvas.getObjects();
for (const obj of objects) {
canvas.remove(obj);
}
this.canvas.clear();
}
}
export default class OperationHistory {
protected undoStack: OperationAction[];
protected redoStack: OperationAction[];
protected canvas: Canvas;
constructor(canvas: Canvas) {
this.undoStack = [];
this.redoStack = [];
this.canvas = canvas;
}
getCanvas() {
return this.canvas;
}
redo(): boolean {
if (this.redoStack.length > 0) {
const opr = this.redoStack.pop()!;
opr.redo();
this.undoStack.push(opr!);
return true;
}
return false;
}
undo(): boolean {
if (this.undoStack.length > 0) {
const opr = this.undoStack.pop()!;
opr.undo();
this.redoStack.push(opr);
return true;
}
return false;
}
recordCreateAction(object: FabricObject) {
this.undoStack.push(new CreateAction(this.canvas, object));
this.clearRedoStack();
}
recordRemoveAction(object: FabricObject) {
this.undoStack.push(new RemoveAction(this.canvas, object));
this.clearRedoStack();
}
recordMoveAction(object: FabricObject, previousX: number, previousY: number) {
this.undoStack.push(new MoveAction(this.canvas, object, previousX, previousY));
this.clearRedoStack();
}
// 放缩可能会改变图形的位置
recordScaleAction(object: FabricObject, previousWidth: number, previousHeight: number,
previousX: number, previousY: number) {
this.undoStack.push(new ScaleAction(this.canvas, object, previousWidth, previousHeight, previousX, previousY));
this.clearRedoStack();
}
recordEllipseScaleAction(object: Ellipse, previousWidth: number, previousHeight: number,
previousX: number, previousY: number, previousRX: number, previousRY: number) {
this.undoStack.push(new EllipseScaleAction(this.canvas, object, previousWidth, previousHeight, previousX, previousY, previousRX, previousRY));
this.clearRedoStack();
}
recordRatioScaleAction(object: FabricObject, previousScaleX: number, previousScaleY: number,
previousX: number, previousY: number) {
this.undoStack.push(new RatioScaleAction(this.canvas, object, previousScaleX, previousScaleY,
previousX, previousY));
this.clearRedoStack();
}
recordFlipXAction(previous: FlipXUndoProps, current: FlipXUndoProps) {
this.undoStack.push(new FlipXAction(this.canvas, previous, current));
this.clearRedoStack();
}
recordFlipYAction(previous: FlipYUndoProps, current: FlipYUndoProps) {
this.undoStack.push(new FlipYAction(this.canvas, previous, current));
this.clearRedoStack();
}
recordRotateAction(previous: RotateProps, current: RotateProps) {
this.undoStack.push(new RotationAction(this.canvas, previous, current));
this.clearRedoStack();
}
recordCropAction(wrapper: CanvasDetailedProps, canvas: FabricCanvasProps, cropWrapper: CanvasDetailedProps, cropCanvas: FabricCanvasProps) {
this.undoStack.push(new CropAction(this.canvas, wrapper, canvas, cropWrapper, cropCanvas));
this.clearRedoStack();
}
clearRedoStack() {
this.redoStack = [];
}
clearStack(){
this.redoStack = []
this.undoStack = []
}
}
\ No newline at end of file
import { Canvas, FabricImage, FabricObject, Point, StaticCanvas } from "fabric";
import ArrowOperator from "./operator/arrow_operator";
import DrawOperator from "./operator/draw_operator";
import EllipseOperator from "./operator/ellipse_operator";
import { OperatorType } from "./image_editor_operator";
import ElementManager from "./element_manager";
import MosaicOperator from "./operator/mosaic_operator";
import RectangleOperator from "./operator/rect_operator";
import TextOperator from "./operator/text_operator";
import OperationHistory from "./history";
import { Screenshoter } from "./screenshoter";
import { ImageEditorShortcutManager } from "./shortcut_manager";
export default class ImageEditor {
private canvas: Canvas;
private screenshoter: Screenshoter;
private history: OperationHistory;
private operatorType: OperatorType = OperatorType.NONE;
private elementManager: ElementManager;
private rectOperator: RectangleOperator;
private ellipseOperator: EllipseOperator;
private arrowOperator: ArrowOperator;
private drawOperator: DrawOperator;
private mosaicOperator: MosaicOperator;
private textOperator: TextOperator;
private operatorMap = new Map();
readonly initWrapperLeft: string;
readonly initWrapperTop: string;
private shortcutManager: ImageEditorShortcutManager;
constructor(canvas: Canvas, elementManager: ElementManager) {
this.elementManager = elementManager;
this.canvas = canvas;
this.history = new OperationHistory(canvas);
this.rectOperator = new RectangleOperator(this);
this.ellipseOperator = new EllipseOperator(this);
this.arrowOperator = new ArrowOperator(this);
this.drawOperator = new DrawOperator(this);
this.mosaicOperator = new MosaicOperator(this);
this.textOperator = new TextOperator(this);
this.bindOperators();
this.operatorMap.set(OperatorType.RECT, this.rectOperator);
this.operatorMap.set(OperatorType.ELLIPSE, this.ellipseOperator);
this.operatorMap.set(OperatorType.ARROW, this.arrowOperator);
this.operatorMap.set(OperatorType.TEXT, this.textOperator);
this.operatorMap.set(OperatorType.DRAW, this.drawOperator);
this.operatorMap.set(OperatorType.MOSAIC, this.mosaicOperator);
this.canvas.selection = false;
this.screenshoter = new Screenshoter();
const canvasWrapper = elementManager.canvasWrapper;
this.initWrapperLeft = canvasWrapper.style.left;
this.initWrapperTop = canvasWrapper.style.top;
this.shortcutManager = new ImageEditorShortcutManager(this);
}
init() {
this.elementManager.init(this);
this.elementManager.bindEvents();
this.screenshoter.init(this, this.elementManager);
}
bindOperators() {
const rectOperator = this.rectOperator;
this.canvas.on('mouse:down', rectOperator.handleMouseDown.bind(rectOperator));
this.canvas.on('mouse:move', rectOperator.handleMouseMove.bind(rectOperator));
this.canvas.on('mouse:up', rectOperator.handleMouseUp.bind(rectOperator));
const ellipseOperator = this.ellipseOperator;
this.canvas.on('mouse:down', ellipseOperator.handleMouseDown.bind(ellipseOperator));
this.canvas.on('mouse:move', ellipseOperator.handleMouseMove.bind(ellipseOperator));
this.canvas.on('mouse:up', ellipseOperator.handleMouseUp.bind(ellipseOperator));
const arrowOperator = this.arrowOperator;
this.canvas.on('mouse:down', arrowOperator.handleMouseDown.bind(arrowOperator));
this.canvas.on('mouse:move', arrowOperator.handleMouseMove.bind(arrowOperator));
this.canvas.on('mouse:up', arrowOperator.handleMouseUp.bind(arrowOperator));
const textOperator = this.textOperator;
this.canvas.on('mouse:down:before', textOperator.handleMouseDownBefore.bind(textOperator))
this.canvas.on('mouse:down', textOperator.handleMouseDown.bind(textOperator));
this.canvas.on('mouse:up', textOperator.handleMouseUp.bind(textOperator));
}
getCanvas() {
return this.canvas;
}
getActiveOperator() {
return this.operatorMap.get(this.operatorType);
}
getOperatorType() {
return this.operatorType;
}
changeOperatorType(type: OperatorType) {
// 如果要修改的type和当前的是一样的话,那么就不变
if (this.operatorType == type) {
return;
}
const previous = this.operatorType;
const current = type;
switch (previous) {
case OperatorType.MOSAIC: this.mosaicOperator.endMosaicMode(); break
case OperatorType.DRAW: this.drawOperator.endDrawMode(); break;
}
switch (current) {
case OperatorType.MOSAIC: this.mosaicOperator.startMosaicMode(); break
case OperatorType.DRAW: this.drawOperator.startDrawMode(); break;
}
this.operatorType = current;
if (current == OperatorType.NONE) {
this.canvas.getObjects().forEach((obj: FabricObject) => {
// 重新调整完后,要将对象激活一下,这或许是个坑?
this.canvas.setActiveObject(obj);
})
}
}
getHistory(): OperationHistory {
return this.history;
}
setCanvasHeight(height: number) {
this.canvas.setDimensions({ height })
}
setCanvasWidth(width: number) {
this.canvas.setDimensions({ width })
}
setCanvasDims(width: number, height: number) {
this.canvas.setDimensions({ height, width });
}
transformX(fabricLeft: number) {
const x = this.canvas.backgroundImage!.getX()
this.canvas.backgroundImage?.setX(x + fabricLeft)
let objs = this.canvas.getObjects();
if (objs == null) {
objs = [];
}
for (const obj of objs) {
obj.left += fabricLeft;
obj.setCoords();
}
this.canvas.renderAll();
}
transformY(fabricTop: number) {
const y = this.canvas.backgroundImage!.getY()
this.canvas.backgroundImage?.setY(y + fabricTop)
let objs = this.canvas.getObjects();
if (objs == null) {
objs = [];
}
for (const obj of objs) {
obj.top += fabricTop;
obj.setCoords();
}
this.canvas.renderAll();
}
getAreaImageInfo(start: Point, bottom: Point) {
const width = bottom.x - start.x;
const height = bottom.y - start.y;
const tempCanvas = new StaticCanvas(undefined, { width, height });
tempCanvas.add(new FabricImage(this.canvas.lowerCanvasEl, {
left: 0,
top: 0,
}))
const image = tempCanvas.toDataURL({
format: 'png',
left: start.x,
top: start.y,
width, height,
multiplier: 1
});
return image;
}
async renderToCanvas(imageDataUrl: string) {
const canvas = this.canvas;
const objects = canvas.getObjects();
for (const object of objects) {
canvas.remove(object);
}
canvas.backgroundImage = undefined;
canvas.clear();
const elementManger = this.elementManager;
let ret;
await FabricImage.fromURL(imageDataUrl).then(img => {
ret = img;
const width = img.width;
const height = img.height;
canvas.setDimensions({ width, height })
img.setX(0);
img.setY(0);
canvas.backgroundImage = img;
canvas.backgroundColor = '#FFF';
const style = elementManger.getFabricWrapper()!.style;
style.left = '0px';
style.top = '0px';
elementManger.resetWrapper(width, height);
canvas.renderAll();
})
return ret;
}
getScreenshoter() {
return this.screenshoter;
}
// 保存状态,后面还原直接用
storeCanvasState() {
const wrapperInfo = this.elementManager.calculateCanvasWrapper();
const canvasInfo = this.elementManager.calculateCanvasInfo();
return {
wrapper: wrapperInfo,
canvas: canvasInfo
}
}
destory() {
this.shortcutManager.destroy();
}
removeActiveObjects() {
const active = this.canvas!.getActiveObject();
if (active) {
this.canvas.remove(active);
this.history.recordRemoveAction(active);
}
}
}
\ No newline at end of file
export interface ImageEditorOperator {
handleMouseDownBefore?(event: any): void;
handleMouseDown?(event: any): void;
handleMouseMove?(event: any): void;
handleMouseUp?(event: any): void;
}
export interface OperatorProps {
setOperatorSize(width: number): void;
setOperatorColor(color: string): void;
getOperatorSize(): number;
getOperatorColor(): string;
}
export const DEFAULT_COLOR = '#FF0000';
export const DEFAULT_STROKE_WIDTH = 4;
export enum OperatorType {
RECT, ELLIPSE, ARROW, DRAW, TEXT, MOSAIC, NONE
}
import { Canvas, FabricImage } from 'fabric';
import ElementManager from './element_manager';
import ImageEditor from './image_editor';
class ImageEditorHelper {
static currentImageEditor: ImageEditor | undefined;
static dpr = window.devicePixelRatio || 1;
static CANVAS_DEFAULT_WIDTH = 100;
static CANVAS_DEFAULT_HEIGHT = 100;
static createImageEditor(imageUrl: string) {
const elements = this.createElement()
const eleManager = new ElementManager(elements);
const resizer = (canvas: Canvas, width: number, height: number) => {
this.resizeCanvas(canvas, eleManager, width, height);
}
const canvas = this.initCanvas(elements.canvas, imageUrl, resizer);
const editor = new ImageEditor(canvas, eleManager);
editor.init();
return editor;
}
private static createElement(): Record<string, any> {
const width = this.CANVAS_DEFAULT_WIDTH, height = this.CANVAS_DEFAULT_HEIGHT;
const wrapper = document.getElementById("app")!;
wrapper.style.width = '100%';
wrapper.style.height = '100%';
wrapper.style.position = 'absolute';
wrapper.style.visibility = 'hidden';
document.body.appendChild(wrapper);
const toolbar = document.createElement("div");
const toolbarMenu = this.initToolbar(toolbar);
// 不考虑滚动条的事,出现滚动条的话,就给点偏差
// canvasWrapper,包裹画板
const canvasWrapper = document.createElement("div");
canvasWrapper.style.backgroundColor = 'white';
canvasWrapper.style.position = 'relative';
canvasWrapper.style.overflow = 'hidden';
const resizers = this.createCanvasResizer(wrapper);
// 添加一张用于截图的canvas,以及8个resizer
const screenshotCanvas = document.createElement("canvas")
screenshotCanvas.style.display = 'none';
screenshotCanvas.style.left = '0';
screenshotCanvas.style.top = '0';
screenshotCanvas.style.position = 'absolute';
// 通过wrapper的拉伸,应该是可以的拉伸底图,底图是白色的
// 拉伸的过程中看不出来,等拉伸完统一结算
// 拉伸下右不需要考虑太多,拉伸上左要让图片进行偏移
// 给的默认值,不需要考虑太多
const canvas = document.createElement("canvas")
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
canvas.width = width;
canvas.height = height;
canvasWrapper.append(canvas);
wrapper.appendChild(canvasWrapper)
wrapper.appendChild(toolbar)
wrapper.appendChild(screenshotCanvas);
document.body.appendChild(wrapper);
const screenshotResizer = this.createScreenshotResizers(wrapper);
const screenshotToolbar = this.createScreenshotToolbar(wrapper);
const rets = {
...toolbarMenu, canvas, canvasWrapper, ...resizers, toolbar, wrapper, screenshotCanvas, screenshotResizer, screenshotToolbar
} as any;
return rets;
}
static createScreenshotToolbar(parent: HTMLElement) {
const toolbar = document.createElement("div");
const style = toolbar.style;
style.position = 'absolute';
style.backgroundColor = 'rgb(229,230,231)'
style.borderRadius = '4px 4px 4px 4px';
style.height = '24px';
style.width = '64px';
style.paddingTop = '4px';
style.paddingBottom = '4px';
style.display = 'none';
const cancelScreenshot = this.appendMenu(toolbar, './assets/cancel.svg', 8, 0);
const confirmScreenshot = this.appendMenu(toolbar, './assets/confirm.svg', 0, 0);
parent.appendChild(toolbar);
return {
toolbar,
screenshot: {
confirm: confirmScreenshot,
cancel: cancelScreenshot
}
};
}
static createScreenshotResizers(parent: HTMLElement) {
const createResizer = () => {
const resizer = document.createElement("div");
const style = resizer.style;
style.left = '0';
style.top = '0';
style.position = 'absolute';
style.width = '8px';
style.height = '8px';
style.boxSizing = 'border-box';
style.border = '1px solid #19a918'
style.transform = 'translate(-50%,-50%)';
style.display = 'none';
resizer.addEventListener('dragstart', function (event) {
event.preventDefault();
})
resizer.draggable = false;
// style.display = 'none'
parent.appendChild(resizer);
return resizer;
}
const northWest = createResizer();
const north = createResizer();
const northEast = createResizer();
const east = createResizer();
const southEast = createResizer();
const south = createResizer();
const southWest = createResizer();
const west = createResizer();
return {
northWest, north, northEast, east, southEast, south, southWest, west
}
}
// topbar和bottombar都要做固定width,居中
// 不要考虑其它的,尾部也是一样的
private static initToolbar(toolbar: HTMLElement): Record<string, HTMLElement> {
toolbar.style.padding = "8px";
toolbar.style.backgroundColor = "#e5e6e7";
toolbar.style.borderRadius = "4px 4px 4px 4px";
toolbar.style.height = "24px";
toolbar.style.width = "690px";
toolbar.style.position = "absolute";
const ret = {} as any;
ret.rectangleMenu = this.appendMenu(toolbar, './assets/rect.svg');
ret.ellipseMenu = this.appendMenu(toolbar, './assets/circle.svg');
ret.arrowMenu = this.appendMenu(toolbar, './assets/arrow.svg');
ret.drawMenu = this.appendMenu(toolbar, './assets/draw.svg');
ret.textMenu = this.appendMenu(toolbar, './assets/text.svg');
ret.mosaicMenu = this.appendMenu(toolbar, './assets/mosaic.svg');
ret.shrinkMenu = this.appendMenu(toolbar, './assets/shrink.svg', 42);
ret.extendMenu = this.appendMenu(toolbar, './assets/extend.svg');
ret.flipXMenu = this.appendMenu(toolbar, './assets/flipX.svg');
ret.flipYMenu = this.appendMenu(toolbar, './assets/flipY.svg');
ret.rotateCounterClockwiseMenu = this.appendMenu(toolbar, './assets/rotate.svg');
ret.rotateCounterClockwiseMenu.style.transform = 'rotateY(180deg)';
ret.rotateClockwiseMenu = this.appendMenu(toolbar, './assets/rotate.svg');
ret.cropMenu = this.appendMenu(toolbar, './assets/crop.svg');
ret.undoMenu = this.appendMenu(toolbar, './assets/undo.svg', 38);
ret.redoMenu = this.appendMenu(toolbar, './assets/redo.svg');
ret.resetMenu = this.appendMenu(toolbar, './assets/reset.svg');
ret.cancaleMenu = this.appendMenu(toolbar, './assets/cancel.svg', 36);
ret.confirmMenu = this.appendMenu(toolbar, './assets/confirm.svg', 0, 0);
return ret;
}
private static appendMenu(topbar: HTMLElement, url: string, marginLeft = 0, marginRight = 8): HTMLElement {
const menu = document.createElement("div")
menu.style.display = "inline-block";
menu.style.width = "24px";
menu.style.height = "24px";
menu.style.marginRight = marginRight + 'px';
menu.style.borderRadius = "4px"
menu.style.lineHeight = "1";
if (marginLeft != 0) {
menu.style.marginLeft = marginLeft + "px"
}
const icon = document.createElement("i");
icon.style.display = "block";
icon.style.width = "24px";
icon.style.height = "24px";
icon.style.backgroundSize = "100% 100%";
icon.style.backgroundRepeat = "no-repeat";
icon.style.cursor = "pointer";
icon.style.opacity = "0.8";
icon.style.backgroundImage = `url('${url}')`
menu.appendChild(icon);
topbar.appendChild(menu);
return menu;
}
private static initCanvas(dom: HTMLCanvasElement, imageUrl: string, resizer: (canvas: Canvas, width: number, height: number) => void): fabric.Canvas {
// 随便给个默认值,后面初始化的时候改掉
const canvas = new Canvas(dom, {
width: this.CANVAS_DEFAULT_WIDTH, height: this.CANVAS_DEFAULT_HEIGHT
})
FabricImage.fromURL(imageUrl).then(img => {
// 使用setX和setY
img.setX(0);
img.setY(0);
canvas.backgroundImage = img;
canvas.backgroundColor = '#FFF';
// 设置完需要渲染一下
canvas.renderAll();
const width = img.width, height = img.height;
resizer(canvas, width, height);
})
return canvas;
}
static resizeCanvas(fbCanvas: Canvas, manager: ElementManager, width: number, height: number) {
const dpr = this.dpr;
const wrapper = manager.wrapper;
const canvasWrapper = manager.canvasWrapper;
const canvas = manager.canvas;
canvasWrapper.style.width = width + 'px';
canvasWrapper.style.height = height + 'px';
// top和left都要好好计算一下
const rect = wrapper.getBoundingClientRect();
const wrapperWidth = rect.width;
const wrapperHeight = rect.height;
let leftOffset = (wrapperWidth - width) / 2;
if (leftOffset <= 20) {
leftOffset = 20;
}
let topOffset = (wrapperHeight - height) / 2;
if (topOffset <= 20) {
topOffset = 20;
}
canvasWrapper.style.left = leftOffset + 'px';
canvasWrapper.style.top = topOffset + 'px';
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
canvas.width = Math.round(width * dpr);
canvas.height = Math.round(height * dpr);
fbCanvas.setDimensions({ width, height })
manager.fixComponentsPosition();
wrapper.style.visibility = 'visible';
}
private static createCanvasResizer(wrapper: HTMLElement) {
const squareSize = 12;
const northResizer = document.createElement('div');
const northWestResizer = document.createElement('div');
const westResizer = document.createElement('div');
const southWestResizer = document.createElement('div');
const southResizer = document.createElement('div');
const southEastResizer = document.createElement('div');
const eastResizer = document.createElement('div');
const northEastResizer = document.createElement('div');
function format(ele: HTMLDivElement) {
ele.style.width = squareSize + 'px';
ele.style.height = squareSize + 'px';
ele.style.backgroundColor = 'white';
ele.style.position = 'absolute'
ele.style.border = 'solid 1px #000';
ele.style.boxSizing = 'border-box'
ele.draggable = false;
ele.addEventListener('dragstart', function (event) {
event.preventDefault();
})
}
format(northResizer);
format(northWestResizer);
format(westResizer);
format(southWestResizer);
format(southResizer);;
format(southEastResizer);
format(eastResizer);
format(northEastResizer);
wrapper.appendChild(northResizer);
wrapper.appendChild(northWestResizer);
wrapper.appendChild(westResizer);
wrapper.appendChild(southWestResizer);
wrapper.appendChild(southResizer);
wrapper.appendChild(southEastResizer);
wrapper.appendChild(eastResizer);
wrapper.appendChild(northEastResizer);
return {
northResizer, northWestResizer, westResizer, southWestResizer,
southResizer, southEastResizer, eastResizer, northEastResizer
}
}
}
ImageEditorHelper.currentImageEditor = ImageEditorHelper.createImageEditor('/basic.jpg');
\ No newline at end of file
import { Canvas } from "fabric";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, ImageEditorOperator, OperatorProps, OperatorType } from "../image_editor_operator";
import Arrow from "./fabric_arrow";
import FabricObjectChangeHelper from "./move_helper";
export default class ArrowOperator implements ImageEditorOperator, OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private start: boolean;
private current: Arrow | undefined = undefined;
private startX: number;
private startY: number;
private color: string = DEFAULT_COLOR;
private strokeWidth: number = DEFAULT_STROKE_WIDTH;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.start = false;
this.startX = 0;
this.startY = 0;
}
getOperatorSize(): number {
return this.strokeWidth;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(width: number): void {
this.strokeWidth = width;
}
setOperatorColor(color: string): void {
this.color = color;
}
handleMouseDown(event: any): void {
let refuse = this.canvas.getActiveObject() != undefined || this.start;
refuse = refuse || this.imageEditor.getOperatorType() != OperatorType.ARROW;
if (refuse) {
return
}
this.start = true;
const canvas = this.canvas;
canvas.requestRenderAll();
let point = canvas.getScenePoint(event.e);
const points: [number, number, number, number] = [point.x, point.y, point.x, point.y];
this.startX = point.x;
this.startY = point.y;
const arrow = new Arrow(points, {
strokeWidth: this.strokeWidth,
stroke: this.color,
lockScalingFlip: true
})
this.current = arrow;
canvas.add(arrow);
}
handleMouseMove(event: any): void {
if (!this.start) {
return;
}
const pointer = this.canvas.getScenePoint(event.e);
this.current?.set({
x2: pointer.x,
y2: pointer.y
})
this.canvas.renderAll();
}
handleMouseUp(event: any): void {
if (this.imageEditor.getOperatorType() != OperatorType.ARROW || !this.start) {
return;
}
this.start = false;
const canvas = this.canvas;
const pointer = canvas.getScenePoint(event.e);
const notMeetMin = Math.abs(pointer.x - this.startX) < 8 && Math.abs(pointer.y - this.startY) < 8;
if (notMeetMin && this.current) {
this.canvas.remove(this.current);
} else {
const lastXY = this.current?.getXY();
const lastSize = {
width: this.current!.width,
height: this.current!.height
}
this.current!.set('lastXY', lastXY);
this.current!.set('lastDim', lastSize);
FabricObjectChangeHelper.listenMove(this.current!, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenScale(this.current!, this.imageEditor.getHistory());
this.imageEditor.getHistory().recordCreateAction(this.current!);
}
}
}
\ No newline at end of file
import { BaseBrush, Canvas, PencilBrush, Shadow } from "fabric";
import OperationHistory from "../history";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, OperatorProps } from "../image_editor_operator";
import FabricObjectChangeHelper from "./move_helper";
export default class DrawOperator implements OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private history: OperationHistory;
private color: string = DEFAULT_COLOR;
private strokeWidth: number = DEFAULT_STROKE_WIDTH;
private recorder: (event: any) => void;
private brush: BaseBrush | undefined;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.history = imageEditor.getHistory();
this.recorder = this.recordPathCreate.bind(this);
}
getOperatorSize(): number {
return this.strokeWidth;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(width: number): void {
this.strokeWidth = width;
this.brush!.width = width;
}
setOperatorColor(color: string): void {
this.color = color;
this.brush!.color = color;
}
recordPathCreate(event: any) {
const path = event.path;
path.hoverCursor = 'default';
path.lockScalingFlip = true;
this.canvas.renderAll();
const lastXY = path.getXY();
const lastScale = {
x: path.scaleX,
y: path.scaleY
}
path.set('lastXY', lastXY);
path.set('lastScale', lastScale);
FabricObjectChangeHelper.listenMove(path, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenRatioScale(path, this.imageEditor.getHistory());
this.history.recordCreateAction(path);
}
startDrawMode(): void {
const canvas = this.canvas;
canvas.isDrawingMode = true;
canvas.freeDrawingBrush = new PencilBrush(canvas);
this.brush = canvas.freeDrawingBrush;
let brush = canvas.freeDrawingBrush;
brush.color = this.color;
brush.width = this.strokeWidth;
brush.shadow = new Shadow({ blur: 2, offsetX: 0, offsetY: 0, color: '#333' })
this.canvas.on('path:created', this.recorder);
}
endDrawMode(): void {
this.canvas.isDrawingMode = false;
this.canvas.off('path:created', this.recorder);
}
}
\ No newline at end of file
import { Canvas, Ellipse } from "fabric";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, ImageEditorOperator, OperatorProps, OperatorType } from "../image_editor_operator";
import FabricObjectChangeHelper from "./move_helper";
export default class EllipseOperator implements ImageEditorOperator, OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private start: boolean;
private current: Ellipse | undefined = undefined;
private startX: number;
private startY: number;
private strokeWidth: number = DEFAULT_STROKE_WIDTH;
private color: string = DEFAULT_COLOR;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.start = false;
this.startX = 0;
this.startY = 0;
}
getOperatorSize(): number {
return this.strokeWidth;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(width: number): void {
this.strokeWidth = width;
}
setOperatorColor(color: string): void {
this.color = color;
}
handleMouseDown(event: any): void {
const canvas = this.canvas;
if (canvas.getActiveObject() != undefined) {
return;
}
if (this.imageEditor.getOperatorType() != OperatorType.ELLIPSE) {
return;
}
if (this.start) {
return;
}
this.start = true;
let pointer = canvas.getScenePoint(event.e);
this.startX = pointer.x;
this.startY = pointer.y;
this.current = new Ellipse({
left: this.startX,
top: this.startY,
rx: 0,
ry: 0,
fill: 'transparent',
stroke: this.color,
strokeWidth: this.strokeWidth,
lockScalingFlip: true
})
canvas.add(this.current);
}
handleMouseMove(event: any): void {
if (!this.start) {
return;
}
let pointer = this.canvas.getScenePoint(event.e);
let rx = Math.abs(pointer.x - this.startX) / 2;
let ry = Math.abs(pointer.y - this.startY) / 2;
if (rx > this.strokeWidth / 2) {
rx = rx - this.strokeWidth / 2
}
if (ry > this.strokeWidth / 2) {
ry = ry - this.strokeWidth / 2
}
let top = pointer.y < this.startY ? pointer.y : this.startY;
let left = pointer.x < this.startX ? pointer.x : this.startX;
this.current?.set('rx', rx);
this.current?.set('ry', ry);
this.current?.set('top', top);
this.current?.set('left', left);
this.canvas.requestRenderAll();
}
handleMouseUp(event: any): void {
if (!this.start || this.imageEditor.getOperatorType() != OperatorType.ELLIPSE) {
return
}
this.start = false;
let pointer = this.canvas.getScenePoint(event.e);
if (pointer.x == this.startX || pointer.y == this.startY) {
this.canvas.remove(this.current!);
} else {
const lastXY = this.current?.getXY();
const lastSize = {
width: this.current!.width,
height: this.current!.height
}
const lastRXY = {
rx: this.current!.rx,
ry: this.current!.ry
}
this.current!.set('lastXY', lastXY);
this.current!.set('lastDim', lastSize);
this.current!.set('lastRXY', lastRXY);
FabricObjectChangeHelper.listenMove(this.current!, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenEllipseScale(this.current!, this.imageEditor.getHistory());
this.imageEditor.getHistory().recordCreateAction(this.current!);
}
this.current = undefined;
}
}
\ No newline at end of file
/* eslint-disable no-param-reassign */
import { Line } from 'fabric';
// 原来的createClass不太好使了,现在改成使用extends
// 由上到下,由左到右
export default class Arrow extends Line {
private arrowWidth: number = 4;
constructor([x1, y1, x2, y2] = [0, 0, 0, 0], options: any = {}) {
super([x1, y1, x2, y2], options);
}
// 8种情况的分解 是在所难免的
_render(ctx: CanvasRenderingContext2D): void {
super._render(ctx);
ctx.save();
// 角度要重新计算一下,应该根据宽高来搞
const xDiff = this.x2 - this.x1;
const yDiff = this.y2 - this.y1;
let y = yDiff > 0 ? this.height : -this.height;
let x = xDiff > 0 ? this.width : -this.width;
if (xDiff == 0) {
x = 0;
}
if (yDiff == 0) {
y = 0;
}
const angle = Math.atan2(y, x);
// 画一个三角形,然后将其移动到合适的位置去
this.translateArrow(ctx);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(this.arrowWidth * 2, 0);
ctx.lineTo(-this.arrowWidth * 2, this.arrowWidth * 2);
ctx.lineTo(-this.arrowWidth * 2, -this.arrowWidth * 2);
ctx.closePath();
ctx.fillStyle = (String)(this.stroke);
ctx.fill();
ctx.restore();
}
translateArrow(ctx: CanvasRenderingContext2D) {
const diffX = this.x2 - this.x1, diffY = this.y2 - this.y1;
if (diffX == 0 && diffY > 0) {
ctx.translate(0, this.height / 2);
} else if (diffX == 0 && diffY < 0) {
ctx.translate(0, -this.height / 2);
} else if (diffY == 0 && diffX > 0) {
ctx.translate(this.width / 2, 0);
} else if (diffY == 0 && diffX < 0) {
ctx.translate(-this.width / 2, 0);
} else if (diffX > 0 && diffY > 0) {
ctx.translate(this.width / 2, this.height / 2);
} else if (diffX > 0 && diffY < 0) {
ctx.translate(this.width / 2, -this.height / 2);
} else if (diffX < 0 && diffY > 0) {
ctx.translate(-this.width / 2, this.height / 2);
} else if (diffX < 0 && diffY < 0) {
ctx.translate(-this.width / 2, -this.height / 2);
}
}
}
\ No newline at end of file
import { Canvas, PatternBrush, Shadow } from "fabric";
import ImageEditor from "../image_editor";
import OperationHistory from "../history";
import { DEFAULT_COLOR, OperatorProps } from "../image_editor_operator";
const blockSize = 5;
function mosaicify(imageData: any) {
const { data } = imageData;
const iLen = imageData.height;
const jLen = imageData.width;
let index;
let i, _i, _iLen, j, _j, _jLen, r, g, b, a;
for (i = 0; i < iLen; i += blockSize) {
for (j = 0; j < jLen; j += blockSize) {
index = i * 4 * jLen + j * 4;
r = data[index];
g = data[index + 1];
b = data[index + 2];
a = data[index + 3];
_iLen = Math.min(i + blockSize, iLen);
_jLen = Math.min(j + blockSize, jLen);
for (_i = i; _i < _iLen; _i++) {
for (_j = j; _j < _jLen; _j++) {
index = _i * 4 * jLen + _j * 4;
data[index] = r;
data[index + 1] = g;
data[index + 2] = b;
data[index + 3] = a;
}
}
}
}
}
export default class MosaicOperator implements OperatorProps {
private canvas: Canvas;
private history: OperationHistory;
// 10小 20中 40大
private width: number = 20;
private mosaicBrush: PatternBrush | undefined;
private recorder: (event: any) => void;
constructor(imageEditor: ImageEditor) {
this.canvas = imageEditor.getCanvas();
this.history = imageEditor.getHistory();
this.recorder = this.recordPathCreate.bind(this);
}
getOperatorSize(): number {
return this.width;
}
getOperatorColor(): string {
return DEFAULT_COLOR;
}
setOperatorSize(width: number): void {
this.width = width;
this.mosaicBrush!.width = width;
}
setOperatorColor(): void {
// ignore
}
recordPathCreate(event: any) {
const path = event.path;
path.selectable = false;
path.evented = false;
path.hoverCursor = 'default';
path.lockScalingFlip = true
this.canvas.renderAll();
this.history.recordCreateAction(path);
}
startMosaicMode() {
const canvas = this.canvas;
canvas.isDrawingMode = true;
const mosaicBrush = new PatternBrush(canvas);
this.mosaicBrush = mosaicBrush;
canvas.freeDrawingBrush = mosaicBrush;
mosaicBrush.width = this.width;
mosaicBrush.shadow = new Shadow({
blur: 0,
offsetX: 0,
offsetY: 0,
affectStroke: true,
});
mosaicBrush.getPatternSrc = function () {
// 创立一个暂存 canvas 来绘製要画的图案
const cropping = {
left: 0,
top: 0,
width: canvas.width,
height: canvas.height,
};
const imageCanvas = canvas.toCanvasElement(1, cropping);
const imageCtx = imageCanvas.getContext('2d')!;
const imageData = imageCtx.getImageData(
0,
0,
imageCanvas.width,
imageCanvas.height,
);
mosaicify(imageData);
imageCtx.putImageData(imageData, 0, 0);
const patternCanvas = document.createElement('canvas'); // 这里的ceateElement一定要使用fabric内置的方法
const patternCtx = patternCanvas.getContext('2d')!;
patternCanvas.width = canvas.width || 0;
patternCanvas.height = canvas.height || 0;
patternCtx.drawImage(
imageCanvas,
0,
0,
imageCanvas.width,
imageCanvas.height,
cropping.left,
cropping.top,
cropping.width,
cropping.height,
);
return patternCanvas;
};
this.canvas.on('path:created', this.recorder);
}
endMosaicMode() {
this.canvas.isDrawingMode = false;
this.canvas.off('path:created', this.recorder);
}
}
\ No newline at end of file
import { Ellipse, FabricObject } from "fabric";
import OperationHistory from "../history";
export default class FabricObjectChangeHelper {
static listenMove(obj: FabricObject, history: OperationHistory) {
obj.on('moving', () => {
if (!obj.get('movingFlag')) {
obj.set('movingFlag', true);
}
})
obj.on('mouseup', () => {
if (!obj.get('movingFlag')) {
return;
}
obj.set('movingFlag', undefined);
const pos = obj.get('lastXY');
obj.set('lastXY', obj.getXY());
history.recordMoveAction(obj, pos.x, pos.y);
})
}
static listenScale(obj: FabricObject, history: OperationHistory) {
obj.on('scaling', () => {
if (!obj.get('scalingFlag')) {
obj.set('scalingFlag', true);
}
const { scaleX, scaleY } = obj;
// 这个地方还不能四舍五入,在结束的地方四舍五入比较好
// 四舍五入导致向左上拉的时候,位置会出现偏差
obj.set({
width: obj.width * scaleX,
height: obj.height * scaleY,
scaleX: 1, // 重置缩放
scaleY: 1
});
// 缓存会导致图像不能正确的放缩
obj.objectCaching = false;
})
obj.on('mouseup', () => {
// 还原缓存
obj.objectCaching = true;
const flag = obj.get('scalingFlag')
if (!flag) {
return
}
obj.set('scalingFlag', undefined);
const dim = obj.get('lastDim');
const pos = obj.get('lastXY');
const width = dim.width;
const height = dim.height;
const currWidth = obj.width;
const currHeight = obj.height;
const currDim = {
width: currWidth,
height: currHeight
}
obj.set('lastDim', currDim);
obj.set('lastXY', obj.getXY());
history.recordScaleAction(obj, width, height, pos.x, pos.y)
})
}
static listenEllipseScale(obj: Ellipse, history: OperationHistory) {
obj.on('scaling', () => {
if (!obj.get('scalingFlag')) {
obj.set('scalingFlag', true);
}
const { scaleX, scaleY } = obj;
const strokeWidth = obj.strokeWidth;
let rx = obj.width * scaleX / 2;
let ry = obj.height * scaleY / 2;
if (rx > strokeWidth / 2) {
rx = rx - strokeWidth / 2;
}
if (ry > strokeWidth / 2) {
ry = ry - strokeWidth / 2;
}
obj.set({
rx, ry,
width: obj.width * scaleX,
height: obj.height * scaleY,
scaleX: 1, // 重置缩放
scaleY: 1
});
// 缓存会导致图像不能正确的放缩
obj.objectCaching = false;
})
obj.on('mouseup', () => {
// 还原缓存
obj.objectCaching = true;
const flag = obj.get('scalingFlag')
if (!flag) {
return
}
obj.set('scalingFlag', undefined);
const dim = obj.get('lastDim');
const pos = obj.get('lastXY');
const rxy = obj.get('lastRXY');
console.log(rxy)
const width = dim.width;
const height = dim.height;
const rx = rxy.rx;
const ry = rxy.ry;
const currWidth = obj.width;
const currHeight = obj.height;
const currDim = {
width: currWidth,
height: currHeight
}
const currRXY = {
rx: obj.rx,
ry: obj.ry
}
obj.set('lastDim', currDim);
obj.set('lastXY', obj.getXY());
obj.set('lastRXY', currRXY)
history.recordEllipseScaleAction(obj, width, height, pos.x, pos.y, rx, ry)
})
}
// 框选,椭圆,箭头,都不是等比例缩放
// 文字、画笔是等比例缩放,要考虑一下等比例缩放的问题
static listenRatioScale(obj: FabricObject, history: OperationHistory) {
obj.on('scaling', () => {
if (!obj.get('scalingFlag')) {
obj.set('scalingFlag', true);
}
})
obj.on('mouseup', () => {
// 还原缓存
obj.objectCaching = true;
const flag = obj.get('scalingFlag')
if (!flag) {
return
}
obj.set('scalingFlag', undefined);
const scale = obj.get('lastScale');
const pos = obj.get('lastXY');
const scaleX = scale.x;
const scaleY = scale.y;
const currScale = {
x: obj.scaleX,
y: obj.scaleY
}
obj.set('lastScale', currScale);
obj.set('lastXY', obj.getXY());
history.recordRatioScaleAction(obj, scaleX, scaleY, pos.x, pos.y)
})
}
}
\ No newline at end of file
import { Canvas, Rect } from "fabric";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, DEFAULT_STROKE_WIDTH, ImageEditorOperator, OperatorProps, OperatorType } from "../image_editor_operator";
import FabricObjectChangeHelper from "./move_helper";
export default class RectangleOperator implements ImageEditorOperator, OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private start: boolean;
private startX: number;
private startY: number;
private strokeWidth: number = DEFAULT_STROKE_WIDTH;
private color: string = DEFAULT_COLOR;
private current: Rect | undefined;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.start = false;
this.startX = 0;
this.startY = 0;
}
getOperatorSize(): number {
return this.strokeWidth;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(width: number): void {
this.strokeWidth = width;
}
setOperatorColor(color: string): void {
this.color = color;
}
handleMouseDown(event: any): void {
const canvas = this.canvas;
if (canvas.getActiveObject() != undefined) {
return;
}
if (this.imageEditor.getOperatorType() != OperatorType.RECT) {
return;
}
if (this.start) {
return;
}
this.start = true;
let pointer = canvas.getScenePoint(event.e);
this.startX = pointer.x;
this.startY = pointer.y;
this.current = new Rect({
left: this.startX,
top: this.startY,
width: 0,
height: 0,
fill: 'transparent',
stroke: this.color,
strokeWidth: this.strokeWidth,
lockScalingFlip: true
})
canvas.add(this.current);
}
handleMouseMove(event: any): void {
if (!this.start) {
return;
}
let pointer = this.canvas.getScenePoint(event.e);
let width = Math.abs(pointer.x - this.startX);
let height = Math.abs(pointer.y - this.startY);
const left = pointer.x < this.startX ? pointer.x : this.startX;
const top = pointer.y < this.startY ? pointer.y : this.startY;
this.current?.set('width', Math.round(width));
this.current?.set('height', Math.round(height));
this.current?.set('top', Math.round(top));
this.current?.set('left', Math.round(left));
this.canvas.requestRenderAll();
}
handleMouseUp(event: any): void {
if (!this.start || this.imageEditor.getOperatorType() != OperatorType.RECT) {
return;
}
this.start = false;
let pointer = this.canvas.getScenePoint(event.e);
let width = Math.abs(pointer.x - this.startX);
let height = Math.abs(pointer.y - this.startY);
if (width <= 0 || height <= 0) {
this.canvas.remove(this.current!);
} else {
const lastXY = this.current?.getXY();
const lastSize = {
width: this.current!.width,
height: this.current!.height
}
this.current!.set('lastXY', lastXY);
this.current!.set('lastDim', lastSize);
FabricObjectChangeHelper.listenMove(this.current!, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenScale(this.current!, this.imageEditor.getHistory());
this.imageEditor.getHistory().recordCreateAction(this.current!);
}
}
}
\ No newline at end of file
import { Canvas, IText } from "fabric";
import ImageEditor from "../image_editor";
import { DEFAULT_COLOR, ImageEditorOperator, OperatorProps, OperatorType } from "../image_editor_operator";
import FabricObjectChangeHelper from "./move_helper";
export default class TextOperator implements ImageEditorOperator, OperatorProps {
private imageEditor: ImageEditor;
private canvas: Canvas;
private start: boolean = false;
private startX: number;
private startY: number;
private allowCreate: boolean = true;
private fontSize: number = 20;
private color: string = DEFAULT_COLOR;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.canvas = imageEditor.getCanvas();
this.startX = 0;
this.startY = 0;
}
getOperatorSize(): number {
return this.fontSize;
}
getOperatorColor(): string {
return this.color;
}
setOperatorSize(fontSize: number): void {
this.fontSize = fontSize;
}
setOperatorColor(color: string): void {
this.color = color;
}
handleMouseDownBefore(): void {
if (this.imageEditor.getOperatorType() != OperatorType.TEXT) {
return
}
if (this.canvas.getActiveObject()) {
this.allowCreate = false;
} else {
this.allowCreate = true;
}
}
handleMouseDown(event: any): void {
if (!this.allowCreate) {
return;
}
const canvas = this.canvas;
if (canvas.getActiveObject() || this.imageEditor.getOperatorType() != OperatorType.TEXT) {
return;
}
const pointer = canvas.getScenePoint(event.e);
this.startX = pointer.x;
this.startY = pointer.y;
this.start = true;
}
handleMouseUp(event: any): void {
if (!this.allowCreate) {
return;
}
if (!this.start || this.imageEditor.getOperatorType() != OperatorType.TEXT) {
return
}
this.start = false;
const canvas = this.canvas;
const pointer = canvas.getScenePoint(event.e);
const width = Math.abs(pointer.x - this.startX);
const height = Math.abs(pointer.y - this.startY);
if (width * width + height * height > 100) {
return;
}
const text = new IText('请输入内容', {
left: pointer.x,
top: pointer.y,
fontSize: this.fontSize,
fill: this.color,
lockScalingFlip: true
} as any);
text.setControlVisible('mt', false);
text.setControlVisible('mb', false);
text.setControlVisible('ml', false);
text.setControlVisible('mr', false);
canvas.add(text);
canvas.setActiveObject(text);
canvas.renderAll();
const lastXY = text.getXY();
const lastScale = {
x: text.scaleX,
y: text.scaleY
}
text.set('lastXY', lastXY);
text.set('lastScale', lastScale);
FabricObjectChangeHelper.listenMove(text, this.imageEditor.getHistory());
FabricObjectChangeHelper.listenRatioScale(text, this.imageEditor.getHistory());
this.imageEditor.getHistory().recordCreateAction(text);
}
}
\ No newline at end of file
此差异已折叠。
import ImageEditor from "./image_editor";
export class ImageEditorShortcutManager {
protected imageEditor: ImageEditor;
protected keyboardEventHandler: (event: KeyboardEvent) => void;
constructor(imageEditor: ImageEditor) {
this.imageEditor = imageEditor;
this.keyboardEventHandler = this.handleKeyboardEvent.bind(this);
document.addEventListener('keydown', this.keyboardEventHandler)
}
handleKeyboardEvent(event: KeyboardEvent) {
const noControlKey = !event.ctrlKey && !event.shiftKey && !event.altKey
const ctrlOnly = event.ctrlKey && !event.shiftKey && !event.altKey;
if (event.key === 'Delete' && noControlKey) {
this.imageEditor.removeActiveObjects();
} else if (ctrlOnly) {
console.log(this.imageEditor)
switch (event.key) {
case 'z':
this.imageEditor!.getHistory().undo();
break;
case 'y':
this.imageEditor!.getHistory().redo();
break;
}
}
}
destroy() {
document.removeEventListener('keydown', this.keyboardEventHandler);
}
}
\ No newline at end of file
export function getAbsolutePosition(element: any) {
const rect = element.getBoundingClientRect();
// 结合页面的滚动距离来计算相对于整个文档的绝对位置
const x = rect.left + window.scrollX;
const y = rect.top + window.scrollY;
return { x, y };
}
\ No newline at end of file
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
import { defineConfig } from 'vite'
import { viteStaticCopy } from 'vite-plugin-static-copy'
export default defineConfig({
plugins: [
viteStaticCopy({
targets: [
{ src: 'src/assets/*', dest: 'assets/' }, // 将 src/assets 下的文件复制到 dist/assets
{ src: 'basic.jpg', dest: '.' }, // 将 src/assets 下的文件复制到 dist/assets
]
})
],
build: {
}
})
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册