├── docs └── packages │ ├── README.md │ ├── .nojekyll │ ├── modules.md │ ├── interfaces │ ├── svg_drawing_core │ │ └── ConvertOption.md │ └── svg_drawing_img_trace │ │ ├── BlurOption.md │ │ ├── FromImageDataOptions.md │ │ ├── Rgba.md │ │ └── ImgTraceOption.md │ ├── modules │ ├── svg_drawing_img_trace.md │ ├── svg_drawing_animation.md │ └── svg_drawing_react.md │ └── classes │ ├── svg_drawing_core │ ├── Renderer.md │ ├── Vector.md │ ├── ResizeHandler.md │ ├── Convert.md │ ├── DrawHandler.md │ ├── Point.md │ ├── Path.md │ ├── Command.md │ ├── Svg.md │ └── SvgDrawing.md │ ├── svg_drawing_img_trace │ ├── Blur.md │ ├── ImgLoader.md │ ├── Palette.md │ └── ImgTrace.md │ └── svg_drawing_animation │ └── SvgAnimation.md ├── .gitattributes ├── examples ├── react │ ├── .eslintignore │ ├── config │ │ ├── paths.ts │ │ └── theme.ts │ ├── public │ │ └── img_trace │ │ │ ├── cat.jpg │ │ │ ├── kuma.jpg │ │ │ ├── risu.jpg │ │ │ ├── panda.png │ │ │ ├── tanuki.jpg │ │ │ └── harinezumi.jpg │ ├── .babelrc.js │ ├── modules.d.ts │ ├── next.config.js │ ├── next-env.d.ts │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ └── demo │ │ │ ├── animation.tsx │ │ │ └── img-trace.tsx │ ├── lib │ │ ├── gtag.ts │ │ └── example-svg.ts │ ├── tsconfig.json │ ├── package.json │ └── components │ │ └── Layout.tsx └── html │ └── index.html ├── packages ├── react │ ├── src │ │ ├── index.ts │ │ ├── types.ts │ │ └── useDrawing.ts │ ├── tsconfig.lib.json │ ├── rollup.config.js │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── animation │ ├── src │ │ ├── index.ts │ │ ├── types.ts │ │ ├── __snapshots__ │ │ │ └── animation.test.ts.snap │ │ ├── animation.test.ts │ │ └── animation.ts │ ├── tsconfig.lib.json │ ├── rollup.config.js │ ├── tsconfig.json │ └── package.json ├── img-trace │ ├── src │ │ ├── __test__ │ │ │ ├── panda.png │ │ │ └── loadPngData.ts │ │ ├── index.ts │ │ ├── utils │ │ │ └── convertRGBAImage.ts │ │ ├── palette.test.ts │ │ ├── trace.test.ts │ │ ├── imgloader.ts │ │ ├── blur.ts │ │ └── palette.ts │ ├── tsconfig.lib.json │ ├── rollup.config.js │ ├── tsconfig.json │ ├── package.json │ └── README.md └── core │ ├── tsconfig.lib.json │ ├── rollup.config.js │ ├── src │ ├── index.ts │ ├── convert.test.ts │ ├── utils.ts │ ├── throttle.ts │ ├── convert.ts │ ├── __snapshots__ │ │ ├── drawing.test.ts.snap │ │ └── svg.test.ts.snap │ ├── drawing.test.ts │ ├── renderer.ts │ ├── types.ts │ ├── download.ts │ ├── drawing.ts │ └── handler.ts │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── lerna.json ├── prettier.config.js ├── .npmignore ├── config └── rollup │ ├── globals.js │ ├── banner.js │ ├── ts-lib.js │ ├── ts-react-lib.js │ └── build.js ├── .editorconfig ├── babel.config.js ├── jest.config.js ├── generate-docs └── package.json ├── .gitignore ├── .github └── workflows │ ├── gh-pages.yml │ └── ci.yml ├── turbo.json ├── tsconfig.json ├── renovate.json ├── LICENSE ├── .eslintrc.js ├── README.md └── package.json /docs/packages/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-vendored 2 | *.html linguist-vendored 3 | -------------------------------------------------------------------------------- /examples/react/.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/* 2 | **/out/* 3 | **/.next/* 4 | -------------------------------------------------------------------------------- /examples/react/config/paths.ts: -------------------------------------------------------------------------------- 1 | export const basePath = process.env.BASE_PATH || '' 2 | -------------------------------------------------------------------------------- /packages/react/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useDrawing' 2 | export * from './types' 3 | -------------------------------------------------------------------------------- /packages/animation/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './animation' 2 | export * from './types' 3 | -------------------------------------------------------------------------------- /examples/react/public/img_trace/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmkzt/svg-drawing/HEAD/examples/react/public/img_trace/cat.jpg -------------------------------------------------------------------------------- /examples/react/public/img_trace/kuma.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmkzt/svg-drawing/HEAD/examples/react/public/img_trace/kuma.jpg -------------------------------------------------------------------------------- /examples/react/public/img_trace/risu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmkzt/svg-drawing/HEAD/examples/react/public/img_trace/risu.jpg -------------------------------------------------------------------------------- /examples/react/public/img_trace/panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmkzt/svg-drawing/HEAD/examples/react/public/img_trace/panda.png -------------------------------------------------------------------------------- /examples/react/public/img_trace/tanuki.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmkzt/svg-drawing/HEAD/examples/react/public/img_trace/tanuki.jpg -------------------------------------------------------------------------------- /packages/img-trace/src/__test__/panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmkzt/svg-drawing/HEAD/packages/img-trace/src/__test__/panda.png -------------------------------------------------------------------------------- /examples/react/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['next/babel'], 3 | plugins: [['styled-components', { ssr: true }]], 4 | } 5 | -------------------------------------------------------------------------------- /examples/react/public/img_trace/harinezumi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmkzt/svg-drawing/HEAD/examples/react/public/img_trace/harinezumi.jpg -------------------------------------------------------------------------------- /packages/img-trace/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './blur' 2 | export * from './imgloader' 3 | export * from './palette' 4 | export * from './trace' 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "useWorkspaces": true, 4 | "registry": "https://registry.npmjs.org", 5 | "version": "4.2.5" 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "**/*.test.ts", "**/__test__/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/react/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "**/*.test.ts", "**/__test__/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['prettier-plugin-jsdoc'], 3 | semi: false, 4 | singleQuote: true, 5 | tsdoc: true, 6 | } 7 | -------------------------------------------------------------------------------- /packages/animation/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "**/*.test.ts", "**/__test__/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/img-trace/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "**/*.test.ts", "**/__test__/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/react/modules.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@rebass/forms/styled-components' { 2 | export type * from '@types/rebass__forms' // eslint-disable-line 3 | } 4 | -------------------------------------------------------------------------------- /docs/packages/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /examples/react/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // For gh-pages 3 | basePath: process.env.BASE_PATH || '', 4 | assetPrefix: process.env.BASE_PATH || '', 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/rollup.config.js: -------------------------------------------------------------------------------- 1 | import tsLib from '../../config/rollup/ts-lib' 2 | import pkg from './package.json' 3 | 4 | export default tsLib({ input: './src/index.ts', pkg }) 5 | -------------------------------------------------------------------------------- /packages/animation/rollup.config.js: -------------------------------------------------------------------------------- 1 | import tsLib from '../../config/rollup/ts-lib' 2 | import pkg from './package.json' 3 | 4 | export default tsLib({ input: './src/index.ts', pkg }) 5 | -------------------------------------------------------------------------------- /packages/img-trace/rollup.config.js: -------------------------------------------------------------------------------- 1 | import tsLib from '../../config/rollup/ts-lib' 2 | import pkg from './package.json' 3 | 4 | export default tsLib({ input: './src/index.ts', pkg }) 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.env 2 | node_modules 3 | .vscode 4 | template.html 5 | webpack.*.js 6 | renovate.json 7 | .travis.yml 8 | src 9 | prettier.config.js 10 | rollup.config.js 11 | tsconfig.json 12 | -------------------------------------------------------------------------------- /config/rollup/globals.js: -------------------------------------------------------------------------------- 1 | export const packageGlobals = { 2 | '@svg-drawing/core': 'SVGDCore', 3 | '@svg-drawing/animation': 'SVGDAnimation', 4 | '@svg-drawing/img-trace': 'SVGDImgTrace', 5 | } 6 | -------------------------------------------------------------------------------- /packages/react/rollup.config.js: -------------------------------------------------------------------------------- 1 | import tsReactLib from '../../config/rollup/ts-react-lib' 2 | import pkg from './package.json' 3 | 4 | export default tsReactLib({ input: './src/index.ts', pkg }) 5 | -------------------------------------------------------------------------------- /examples/react/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/animation/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { RendererOption, Path } from '@svg-drawing/core' 2 | export type AnimationOption = RendererOption & { 3 | ms: number 4 | } 5 | export type FrameAnimation = (origin: Path[], loopIndex?: number) => Path[] 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 2 6 | end_of_line = lf 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /config/rollup/banner.js: -------------------------------------------------------------------------------- 1 | export const createBanner = ({ name, version, author, license }) => 2 | `/*\n` + 3 | ` * ${name} v${version} \n` + 4 | ` * \n` + 5 | ` * Copyright (C) ${author}.\n` + 6 | ` * This source code is licensed under the ${license} license.\n` + 7 | ' */' 8 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './drawing' 2 | export * from './convert' 3 | export * from './renderer' 4 | export * from './svg' 5 | export * from './throttle' 6 | export * from './utils' 7 | export * from './handler' 8 | export * from './types' 9 | export * from './download' 10 | -------------------------------------------------------------------------------- /docs/packages/modules.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## Modules 4 | 5 | - [@svg-drawing/animation](modules/svg_drawing_animation.md) 6 | - [@svg-drawing/core](modules/svg_drawing_core.md) 7 | - [@svg-drawing/img-trace](modules/svg_drawing_img_trace.md) 8 | - [@svg-drawing/react](modules/svg_drawing_react.md) 9 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib", 6 | "declaration": true, 7 | "declarationDir": "./lib", 8 | "emitDeclarationOnly": true 9 | }, 10 | "include": ["src/**/*"], 11 | "references": [] 12 | } 13 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib", 6 | "declaration": true, 7 | "declarationDir": "./lib", 8 | "emitDeclarationOnly": true 9 | }, 10 | "include": ["src/**/*"], 11 | "references": [] 12 | } 13 | -------------------------------------------------------------------------------- /packages/animation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib", 6 | "declaration": true, 7 | "declarationDir": "./lib", 8 | "emitDeclarationOnly": true 9 | }, 10 | "include": ["src/**/*"], 11 | "references": [] 12 | } 13 | -------------------------------------------------------------------------------- /packages/img-trace/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "./lib", 6 | "declaration": true, 7 | "declarationDir": "./lib", 8 | "emitDeclarationOnly": true 9 | }, 10 | "include": ["src/**/*"], 11 | "references": [] 12 | } 13 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * For babel-jest 3 | */ 4 | module.exports = (api) => { 5 | api.cache(false) 6 | return { 7 | presets: [ 8 | [ 9 | '@babel/preset-env', 10 | { 11 | targets: { node: 'current' }, 12 | }, 13 | ], 14 | '@babel/preset-typescript', 15 | ], 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/packages/interfaces/svg_drawing_core/ConvertOption.md: -------------------------------------------------------------------------------- 1 | # Interface: ConvertOption 2 | 3 | [@svg-drawing/core](../../modules/svg_drawing_core.md).ConvertOption 4 | 5 | Convert options 6 | 7 | ## Properties 8 | 9 | ### ratio 10 | 11 | • `Optional` **ratio**: `number` 12 | 13 | #### Defined in 14 | 15 | [core/src/types.ts:49](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/types.ts#L49) 16 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | transform: { 4 | '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest', 5 | }, 6 | transformIgnorePatterns: ['/node_modules/'], 7 | testEnvironment: 'jest-environment-jsdom', 8 | testRegex: '(\\.|/)(test|spec)\\.(t|j)sx?$', 9 | moduleNameMapper: {}, 10 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 11 | collectCoverage: true, 12 | collectCoverageFrom: ['/packages/**/src/**/**.(t|j)s?(x)'], 13 | } 14 | -------------------------------------------------------------------------------- /examples/react/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import App from 'next/app' 2 | import { StrictMode } from 'react' 3 | import { ThemeProvider } from 'styled-components' 4 | import theme from '../config/theme' 5 | export default class MyApp extends App { 6 | render() { 7 | const { Component, pageProps } = this.props 8 | 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/src/convert.test.ts: -------------------------------------------------------------------------------- 1 | import { Convert } from './convert' 2 | 3 | describe('convert.ts', () => { 4 | describe('Convert', () => { 5 | it('bezierCurve', () => { 6 | const convert = new Convert() 7 | expect( 8 | convert 9 | .bezierCurve( 10 | { x: 0, y: 0 }, 11 | { x: 1, y: 1 }, 12 | { x: 2, y: 1 }, 13 | { x: 3, y: 0 } 14 | ) 15 | .toString() 16 | ).toBe('C 1.4 1.2 1.6 1.2 2 1') 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/core/src/utils.ts: -------------------------------------------------------------------------------- 1 | export const camel2kebab = (str: string): string => 2 | str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() 3 | 4 | export const roundUp = (num: number, digits = 2): number => +num.toFixed(digits) 5 | 6 | export const kebab2camel = (str: string): string => 7 | str.replace(/-([a-z])/g, (a: string, b: string) => b.toUpperCase()) 8 | 9 | export const isAlmostSameNumber = (a: number, b: number) => 10 | Math.trunc(a) === Math.trunc(b) 11 | 12 | export const isNaN = (num: number) => num !== num 13 | -------------------------------------------------------------------------------- /examples/react/lib/gtag.ts: -------------------------------------------------------------------------------- 1 | export const GA_TRACKING_ID = 'UA-91428067-3' 2 | 3 | // https://developers.google.com/analytics/devguides/collection/gtagjs/pages 4 | export const pageview = (url: any) => { 5 | // @ts-ignore 6 | window.gtag('config', GA_TRACKING_ID, { 7 | page_path: url, 8 | }) 9 | } 10 | 11 | // https://developers.google.com/analytics/devguides/collection/gtagjs/events 12 | export const event = ({ action, category, label, value }: any) => { 13 | // @ts-ignore 14 | window.gtag('event', action, { 15 | event_category: category, 16 | event_label: label, 17 | value: value, 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /docs/packages/interfaces/svg_drawing_img_trace/BlurOption.md: -------------------------------------------------------------------------------- 1 | # Interface: BlurOption 2 | 3 | [@svg-drawing/img-trace](../../modules/svg_drawing_img_trace.md).BlurOption 4 | 5 | ## Properties 6 | 7 | ### delta 8 | 9 | • `Optional` **delta**: `number` 10 | 11 | #### Defined in 12 | 13 | [img-trace/src/blur.ts:20](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/blur.ts#L20) 14 | 15 | ___ 16 | 17 | ### radius 18 | 19 | • `Optional` **radius**: `number` 20 | 21 | #### Defined in 22 | 23 | [img-trace/src/blur.ts:19](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/blur.ts#L19) 24 | -------------------------------------------------------------------------------- /packages/img-trace/src/utils/convertRGBAImage.ts: -------------------------------------------------------------------------------- 1 | export const convertRGBAImage = (imgd: ImageData): ImageData => { 2 | const pixelnum = imgd.width * imgd.height 3 | const isRGB = imgd.data.length < pixelnum * 4 4 | if (!isRGB) return imgd 5 | 6 | const rgbaImgd = new Uint8ClampedArray(pixelnum * 4) 7 | for (let pxcnt = 0; pxcnt < pixelnum; pxcnt++) { 8 | rgbaImgd[pxcnt * 4] = imgd.data[pxcnt * 3] 9 | rgbaImgd[pxcnt * 4 + 1] = imgd.data[pxcnt * 3 + 1] 10 | rgbaImgd[pxcnt * 4 + 2] = imgd.data[pxcnt * 3 + 2] 11 | rgbaImgd[pxcnt * 4 + 3] = 255 12 | } 13 | return { 14 | ...imgd, 15 | data: rgbaImgd, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /generate-docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svg-drawing-generate-docs", 3 | "private": true, 4 | "version": "4.2.5", 5 | "scripts": { 6 | "build": "typedoc --entryPointStrategy packages ../ --out ../docs/packages --excludePrivate --plugin typedoc-plugin-markdown --hideInPageTOC --hideBreadcrumbs --filenameSeparator \"/\"" 7 | }, 8 | "dependencies": { 9 | "@svg-drawing/animation": "4.2.5", 10 | "@svg-drawing/core": "4.2.5", 11 | "@svg-drawing/img-trace": "4.2.5", 12 | "@svg-drawing/react": "4.2.5" 13 | }, 14 | "devDependencies": { 15 | "typedoc": "0.22.17", 16 | "typedoc-plugin-markdown": "3.12.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/packages/modules/svg_drawing_img_trace.md: -------------------------------------------------------------------------------- 1 | # Module: @svg-drawing/img-trace 2 | 3 | ## Classes 4 | 5 | - [Blur](../classes/svg_drawing_img_trace/Blur.md) 6 | - [ImgLoader](../classes/svg_drawing_img_trace/ImgLoader.md) 7 | - [ImgTrace](../classes/svg_drawing_img_trace/ImgTrace.md) 8 | - [Palette](../classes/svg_drawing_img_trace/Palette.md) 9 | 10 | ## Interfaces 11 | 12 | - [BlurOption](../interfaces/svg_drawing_img_trace/BlurOption.md) 13 | - [FromImageDataOptions](../interfaces/svg_drawing_img_trace/FromImageDataOptions.md) 14 | - [ImgTraceOption](../interfaces/svg_drawing_img_trace/ImgTraceOption.md) 15 | - [Rgba](../interfaces/svg_drawing_img_trace/Rgba.md) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore 2 | *.DS_Store 3 | .cache 4 | 5 | # Trubo 6 | .turbo 7 | 8 | # dependencies 9 | node_modules 10 | .pnp 11 | .pnp.js 12 | 13 | # testing 14 | /coverage 15 | 16 | # production,distfile 17 | build 18 | dist 19 | lib 20 | .size-snapshot.json 21 | .changelog 22 | 23 | # log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | __debug__ 29 | 30 | # configuration for editor 31 | .vscode 32 | .node-version 33 | 34 | # npm lock files 35 | package-lock.json 36 | # yarn.lock 37 | 38 | # next.js 39 | !examples/**/lib 40 | .next 41 | out 42 | .vercel 43 | 44 | # TypeScript 45 | tsconfig.tsbuildinfo 46 | tsconfig.*.tsbuildinfo 47 | -------------------------------------------------------------------------------- /docs/packages/interfaces/svg_drawing_img_trace/FromImageDataOptions.md: -------------------------------------------------------------------------------- 1 | # Interface: FromImageDataOptions 2 | 3 | [@svg-drawing/img-trace](../../modules/svg_drawing_img_trace.md).FromImageDataOptions 4 | 5 | ## Properties 6 | 7 | ### colorQuantCycles 8 | 9 | • `Optional` **colorQuantCycles**: `number` 10 | 11 | #### Defined in 12 | 13 | [img-trace/src/palette.ts:13](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/palette.ts#L13) 14 | 15 | ___ 16 | 17 | ### numberOfColors 18 | 19 | • `Optional` **numberOfColors**: `number` 20 | 21 | #### Defined in 22 | 23 | [img-trace/src/palette.ts:12](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/palette.ts#L12) 24 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: deploy github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy-ghpages: 10 | runs-on: macos-10.15 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: Setup Node 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 16.13.1 17 | - name: Build 18 | run: | 19 | yarn install --frozen-lockfile 20 | yarn build:gh-page 21 | - name: Deploy 22 | uses: peaceiris/actions-gh-pages@v3 23 | with: 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | publish_branch: gh-pages 26 | publish_dir: ./examples/react/out 27 | -------------------------------------------------------------------------------- /examples/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["next-env.d.ts", "modules.d.ts", "**/*.ts", "**/*.tsx"], 4 | "compilerOptions": { 5 | "allowJs": true, 6 | "noEmit": true, 7 | "resolveJsonModule": true, 8 | "isolatedModules": true, 9 | "jsx": "preserve", 10 | "lib": ["dom", "dom.iterable", "esnext"], 11 | "incremental": true 12 | }, 13 | "exclude": ["node_modules"], 14 | "references": [ 15 | { 16 | "path": "../../packages/animation" 17 | }, 18 | { 19 | "path": "../../packages/core" 20 | }, 21 | { 22 | "path": "../../packages/img-trace" 23 | }, 24 | { 25 | "path": "../../packages/react" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/react/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { download, SvgDrawing, DrawingOption } from '@svg-drawing/core' 2 | import type { RefObject } from 'react' 3 | 4 | export type UseSvgDrawing = { 5 | ref: RefObject 6 | clear: () => void 7 | undo: () => void 8 | changePenColor: (penColor: DrawingOption['penColor']) => void 9 | changePenWidth: (penwidth: DrawingOption['penWidth']) => void 10 | changeFill: (penColor: DrawingOption['fill']) => void 11 | changeClose: (penwidth: DrawingOption['close']) => void 12 | changeDelay: (penColor: DrawingOption['delay']) => void 13 | changeCurve: (penwidth: DrawingOption['curve']) => void 14 | getSvgXML: () => string | null 15 | download: (opt: Parameters[1]) => void 16 | } 17 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "baseBranch": "origin/main", 4 | "pipeline": { 5 | "clear": { 6 | "dependsOn": ["^clear"], 7 | "outputs": ["lib/**", "**/*.tsbuildinfo"] 8 | }, 9 | "build": { 10 | "dependsOn": ["^clear", "^build"], 11 | "outputs": ["lib/**", "./next/**", "./out/**"] 12 | }, 13 | "test": { 14 | "dependsOn": ["^build"], 15 | "outputs": ["snapshots/**"] 16 | }, 17 | "typecheck": { 18 | "dependsOn": ["^build"], 19 | "outputs": [] 20 | }, 21 | "lint": { 22 | "dependsOn": ["^build"], 23 | "outputs": [] 24 | }, 25 | "dev": { 26 | "cache": false 27 | }, 28 | "dev:tsc": { 29 | "cache": false 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/img-trace/src/__test__/loadPngData.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs' 2 | // @ts-ignore 3 | // eslint-disable-next-line import/no-unresolved 4 | import PNGReader from 'png.js' 5 | interface PngData { 6 | width: number 7 | height: number 8 | data: any 9 | } 10 | 11 | export const loadPngData = (filepath: string, cb: (png: PngData) => void) => { 12 | readFile(filepath, (err: any, bytes: any) => { 13 | if (err) { 14 | console.log(err) 15 | throw err 16 | } 17 | 18 | const reader = new PNGReader(bytes) 19 | 20 | reader.parse((err: any, png: any) => { 21 | if (err) { 22 | console.log(err) 23 | throw err 24 | } 25 | 26 | cb({ 27 | width: png.width, 28 | height: png.height, 29 | data: png.pixels, 30 | }) 31 | }) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es6", // TODO: change esnext 5 | "module": "esnext", 6 | "lib": ["esnext", "es2020", "es2019", "es2018", "es2017", "dom"], 7 | "jsx": "react", 8 | "moduleResolution": "node", 9 | "composite": true, 10 | "allowSyntheticDefaultImports": true, 11 | "esModuleInterop": true, 12 | "experimentalDecorators": true, 13 | "downlevelIteration": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noEmitOnError": true, 16 | "noUnusedLocals": false, 17 | "removeComments": false, 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "strictNullChecks": true, 21 | "suppressImplicitAnyIndexErrors": true, 22 | "plugins": [], 23 | "typeRoots": ["node_modules/@types"] 24 | }, 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /config/rollup/ts-lib.js: -------------------------------------------------------------------------------- 1 | import build from './build' 2 | 3 | export default build({ 4 | getBabelOptions: ({ esm, extensions, targets }) => ({ 5 | extensions, 6 | babelrc: false, 7 | exclude: '**/node_modules/**', 8 | runtimeHelpers: true, 9 | presets: [ 10 | [ 11 | '@babel/preset-env', 12 | { 13 | targets, 14 | loose: true, 15 | modules: false, 16 | }, 17 | ], 18 | '@babel/preset-typescript', 19 | ], 20 | plugins: [ 21 | // TODO: optimize bundle size 22 | // 'babel-plugin-annotate-pure-calls', 23 | [ 24 | '@babel/plugin-transform-runtime', 25 | { useESModules: esm, regenerator: true }, 26 | ], 27 | ['@babel/plugin-proposal-class-properties', { loose: false }], 28 | ['@babel/plugin-proposal-private-methods', { loose: false }], 29 | ['@babel/plugin-proposal-private-property-in-object', { loose: false }], 30 | ], 31 | }), 32 | }) 33 | -------------------------------------------------------------------------------- /docs/packages/modules/svg_drawing_animation.md: -------------------------------------------------------------------------------- 1 | # Module: @svg-drawing/animation 2 | 3 | ## Classes 4 | 5 | - [SvgAnimation](../classes/svg_drawing_animation/SvgAnimation.md) 6 | 7 | ## Type Aliases 8 | 9 | ### AnimationOption 10 | 11 | Ƭ **AnimationOption**: `RendererOption` & { `ms`: `number` } 12 | 13 | #### Defined in 14 | 15 | [animation/src/types.ts:2](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/types.ts#L2) 16 | 17 | ___ 18 | 19 | ### FrameAnimation 20 | 21 | Ƭ **FrameAnimation**: (`origin`: `Path`[], `loopIndex?`: `number`) => `Path`[] 22 | 23 | #### Type declaration 24 | 25 | ▸ (`origin`, `loopIndex?`): `Path`[] 26 | 27 | ##### Parameters 28 | 29 | | Name | Type | 30 | | :------ | :------ | 31 | | `origin` | `Path`[] | 32 | | `loopIndex?` | `number` | 33 | 34 | ##### Returns 35 | 36 | `Path`[] 37 | 38 | #### Defined in 39 | 40 | [animation/src/types.ts:5](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/types.ts#L5) 41 | -------------------------------------------------------------------------------- /docs/packages/interfaces/svg_drawing_img_trace/Rgba.md: -------------------------------------------------------------------------------- 1 | # Interface: Rgba 2 | 3 | [@svg-drawing/img-trace](../../modules/svg_drawing_img_trace.md).Rgba 4 | 5 | ## Properties 6 | 7 | ### a 8 | 9 | • **a**: `number` 10 | 11 | #### Defined in 12 | 13 | [img-trace/src/palette.ts:7](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/palette.ts#L7) 14 | 15 | ___ 16 | 17 | ### b 18 | 19 | • **b**: `number` 20 | 21 | #### Defined in 22 | 23 | [img-trace/src/palette.ts:6](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/palette.ts#L6) 24 | 25 | ___ 26 | 27 | ### g 28 | 29 | • **g**: `number` 30 | 31 | #### Defined in 32 | 33 | [img-trace/src/palette.ts:5](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/palette.ts#L5) 34 | 35 | ___ 36 | 37 | ### r 38 | 39 | • **r**: `number` 40 | 41 | #### Defined in 42 | 43 | [img-trace/src/palette.ts:4](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/palette.ts#L4) 44 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "labels": ["renovate", "dependencies"], 5 | "major": { "groupName": "all major dependencies" }, 6 | "minor": { "groupName": "all minor dependencies" }, 7 | "patch": { "groupName": "all patch dependencies", "automerge": true }, 8 | "npm": { 9 | "packageRules": [ 10 | { 11 | "groupName": "TypeScript", 12 | "matchPackageNames": ["typescript"], 13 | "matchPaths": ["package.json"] 14 | }, 15 | { 16 | "groupName": "ESLint and Prettier", 17 | "matchPackageNames": ["eslint", "prettier"], 18 | "matchPackagePatterns": ["^eslint-config-", "^eslint-plugin-", "^@typescript-eslint"], 19 | "matchPaths": ["package.json"] 20 | }, 21 | { 22 | "groupName": "Typedoc", 23 | "matchPackageNames": ["typedoc"], 24 | "matchPackagePatterns": ["^typedoc-plugin-"], 25 | "matchPaths": ["package.json"] 26 | } 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_core/Renderer.md: -------------------------------------------------------------------------------- 1 | # Class: Renderer 2 | 3 | [@svg-drawing/core](../../modules/svg_drawing_core.md).Renderer 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new Renderer**(`el`, `__namedParameters?`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `el` | `HTMLElement` | 16 | | `__namedParameters` | [`RendererOption`](../../modules/svg_drawing_core.md#rendereroption) | 17 | 18 | #### Defined in 19 | 20 | [core/src/renderer.ts:74](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/renderer.ts#L74) 21 | 22 | ## Properties 23 | 24 | ### el 25 | 26 | • **el**: `HTMLElement` 27 | 28 | ## Methods 29 | 30 | ### update 31 | 32 | ▸ **update**(`svgObj`): `void` 33 | 34 | #### Parameters 35 | 36 | | Name | Type | 37 | | :------ | :------ | 38 | | `svgObj` | [`SvgObject`](../../modules/svg_drawing_core.md#svgobject) | 39 | 40 | #### Returns 41 | 42 | `void` 43 | 44 | #### Defined in 45 | 46 | [core/src/renderer.ts:80](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/renderer.ts#L80) 47 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@svg-drawing-example/react", 3 | "private": true, 4 | "version": "4.2.5", 5 | "scripts": { 6 | "clear": "rimraf .next out *.tsbuildinfo", 7 | "start": "next start", 8 | "dev": "next", 9 | "build": "next build; next export", 10 | "lint": "eslint . --ext .js,.ts,.tsx", 11 | "fmt": "yarn lint --fix", 12 | "prepare": "build" 13 | }, 14 | "dependencies": { 15 | "@styled-icons/octicons": "10.6.0", 16 | "@svg-drawing/animation": "4.2.5", 17 | "@svg-drawing/core": "4.2.5", 18 | "@svg-drawing/img-trace": "4.2.5", 19 | "@svg-drawing/react": "4.2.5", 20 | "next": "12.1.6", 21 | "react": "18.1.0", 22 | "react-dom": "18.1.0", 23 | "rebass": "4.0.7", 24 | "regenerator-runtime": "0.13.9", 25 | "styled-components": "5.1.1", 26 | "styled-system": "5.1.5" 27 | }, 28 | "devDependencies": { 29 | "@types/rebass": "4.0.6", 30 | "@types/styled-components": "5.1.1", 31 | "@types/styled-system": "5.1.1", 32 | "babel-plugin-styled-components": "1.10.7", 33 | "raw-loader": "4.0.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 kazuto kamei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/rollup/ts-react-lib.js: -------------------------------------------------------------------------------- 1 | import build from './build' 2 | 3 | export default build({ 4 | getBabelOptions: ({ esm, extensions, targets }) => ({ 5 | extensions, 6 | babelrc: false, 7 | exclude: '**/node_modules/**', 8 | runtimeHelpers: true, 9 | presets: [ 10 | [ 11 | '@babel/preset-env', 12 | { 13 | targets, 14 | loose: true, 15 | modules: false, 16 | }, 17 | ], 18 | ['@babel/preset-react', { useBuiltIns: true }], 19 | '@babel/preset-typescript', 20 | ], 21 | plugins: [ 22 | ['transform-react-remove-prop-types', { removeImport: true }], 23 | // TODO: optimize bundle size 24 | // 'babel-plugin-annotate-pure-calls', 25 | [ 26 | '@babel/plugin-transform-runtime', 27 | { useESModules: esm, regenerator: true }, 28 | ], 29 | ['@babel/plugin-proposal-class-properties', { loose: false }], 30 | ['@babel/plugin-proposal-private-methods', { loose: false }], 31 | ['@babel/plugin-proposal-private-property-in-object', { loose: false }], 32 | ], 33 | }), 34 | globals: { 35 | react: 'React', 36 | }, 37 | }) 38 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_img_trace/Blur.md: -------------------------------------------------------------------------------- 1 | # Class: Blur 2 | 3 | [@svg-drawing/img-trace](../../modules/svg_drawing_img_trace.md).Blur 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new Blur**(`__namedParameters`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `__namedParameters` | [`BlurOption`](../../interfaces/svg_drawing_img_trace/BlurOption.md) | 16 | 17 | #### Defined in 18 | 19 | [img-trace/src/blur.ts:25](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/blur.ts#L25) 20 | 21 | ## Properties 22 | 23 | ### delta 24 | 25 | • **delta**: `number` 26 | 27 | #### Defined in 28 | 29 | [img-trace/src/blur.ts:24](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/blur.ts#L24) 30 | 31 | ___ 32 | 33 | ### radius 34 | 35 | • **radius**: `number` 36 | 37 | #### Defined in 38 | 39 | [img-trace/src/blur.ts:23](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/blur.ts#L23) 40 | 41 | ## Methods 42 | 43 | ### apply 44 | 45 | ▸ **apply**(`argimgd`): `ImageData` 46 | 47 | #### Parameters 48 | 49 | | Name | Type | 50 | | :------ | :------ | 51 | | `argimgd` | `ImageData` | 52 | 53 | #### Returns 54 | 55 | `ImageData` 56 | 57 | #### Defined in 58 | 59 | [img-trace/src/blur.ts:30](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/blur.ts#L30) 60 | -------------------------------------------------------------------------------- /packages/core/src/throttle.ts: -------------------------------------------------------------------------------- 1 | interface Options { 2 | leading?: boolean 3 | trailing?: boolean 4 | } 5 | 6 | export function throttle any>( 7 | func: T, 8 | wait: number, 9 | options: Options = {} 10 | ): (...args: Parameters) => ReturnType | null { 11 | let context: any | null 12 | let args: any | null 13 | let result: ReturnType | null 14 | let timeout: any | null = null 15 | let previous = 0 16 | 17 | const later = (): void => { 18 | previous = options.leading === false ? 0 : Date.now() 19 | timeout = null 20 | result = func.apply(context, args) 21 | if (!timeout) { 22 | context = null 23 | args = null 24 | } 25 | } 26 | 27 | const stop = () => { 28 | if (timeout) { 29 | clearTimeout(timeout) 30 | timeout = null 31 | } 32 | } 33 | 34 | return function wrap( 35 | this: typeof func, 36 | ...wraparg: Parameters 37 | ): ReturnType | null { 38 | const now = Date.now() 39 | if (!previous && options.leading === false) previous = now 40 | const remaining = wait - (now - previous) 41 | context = this 42 | args = wraparg 43 | if (remaining <= 0 || remaining > wait) { 44 | stop() 45 | previous = now 46 | result = func.apply(context, args) 47 | if (!timeout) { 48 | context = null 49 | args = null 50 | } 51 | } else if (!timeout && options.trailing !== false) { 52 | timeout = setTimeout(later, remaining) 53 | } 54 | return result 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_core/Vector.md: -------------------------------------------------------------------------------- 1 | # Class: Vector 2 | 3 | [@svg-drawing/core](../../modules/svg_drawing_core.md).Vector 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new Vector**(`v`, `a`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `v` | `number` | 16 | | `a` | `number` | 17 | 18 | #### Defined in 19 | 20 | [core/src/svg.ts:129](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L129) 21 | 22 | ## Properties 23 | 24 | ### angle 25 | 26 | • **angle**: `number` 27 | 28 | #### Defined in 29 | 30 | [core/src/svg.ts:127](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L127) 31 | 32 | ___ 33 | 34 | ### value 35 | 36 | • **value**: `number` 37 | 38 | #### Defined in 39 | 40 | [core/src/svg.ts:128](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L128) 41 | 42 | ## Methods 43 | 44 | ### scale 45 | 46 | ▸ **scale**(`r`): [`Vector`](Vector.md) 47 | 48 | #### Parameters 49 | 50 | | Name | Type | 51 | | :------ | :------ | 52 | | `r` | `number` | 53 | 54 | #### Returns 55 | 56 | [`Vector`](Vector.md) 57 | 58 | #### Defined in 59 | 60 | [core/src/svg.ts:140](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L140) 61 | 62 | ___ 63 | 64 | ### toPoint 65 | 66 | ▸ **toPoint**(): [`Point`](Point.md) 67 | 68 | #### Returns 69 | 70 | [`Point`](Point.md) 71 | 72 | #### Defined in 73 | 74 | [core/src/svg.ts:134](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L134) 75 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@svg-drawing/core", 3 | "version": "4.2.5", 4 | "license": "MIT", 5 | "description": "svg drawing library.", 6 | "author": { 7 | "name": "Kazuto Kamei", 8 | "email": "kmkzt0927@gmail.com" 9 | }, 10 | "keywords": [ 11 | "svg", 12 | "drawing" 13 | ], 14 | "type": "module", 15 | "types": "./lib/index.d.ts", 16 | "main": "./lib/index.js", 17 | "browser": "./lib/index.umd.js", 18 | "exports": { 19 | "import": "./lib/index.js", 20 | "require": "./lib/index.cjs", 21 | "development": "./lib/index.dev.js" 22 | }, 23 | "files": [ 24 | "lib/**/*" 25 | ], 26 | "typedocMain": "src/index.ts", 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/kmkzt/svg-drawing.git", 30 | "directory": "packages/core" 31 | }, 32 | "homepage": "https://github.com/kmkzt/svg-drawing#readme", 33 | "bugs": { 34 | "url": "https://github.com/kmkzt/svg-drawing/issues" 35 | }, 36 | "scripts": { 37 | "dev": "NODE_ENV=development rollup -w -c", 38 | "dev:tsc": "NODE_ENV=development tsc -w", 39 | "build": "npm-run-all -p lib:*", 40 | "lib:rollup": "NODE_ENV=production rollup -c", 41 | "lib:tsc": "NODE_ENV=production tsc -p ./tsconfig.lib.json", 42 | "clear": "rimraf lib/* *.tsbuildinfo", 43 | "typecheck": "tsc", 44 | "lint": "eslint ./src --ext .js,.ts,.tsx", 45 | "fmt": "yarn lint --fix", 46 | "prepare": "yarn build" 47 | }, 48 | "publishConfig": { 49 | "access": "public" 50 | }, 51 | "gitHead": "6811d0236e51e0204ffbf64a6124ca5469079373" 52 | } 53 | -------------------------------------------------------------------------------- /docs/packages/modules/svg_drawing_react.md: -------------------------------------------------------------------------------- 1 | # Module: @svg-drawing/react 2 | 3 | ## Type Aliases 4 | 5 | ### UseSvgDrawing 6 | 7 | Ƭ **UseSvgDrawing**: `Object` 8 | 9 | #### Type declaration 10 | 11 | | Name | Type | 12 | | :------ | :------ | 13 | | `ref` | `RefObject`<`SvgDrawing` \| ``null``\> | 14 | | `changeClose` | (`penwidth`: `undefined` \| `boolean`) => `void` | 15 | | `changeCurve` | (`penwidth`: `undefined` \| `boolean`) => `void` | 16 | | `changeDelay` | (`penColor`: `undefined` \| `number`) => `void` | 17 | | `changeFill` | (`penColor`: `undefined` \| `string`) => `void` | 18 | | `changePenColor` | (`penColor`: `undefined` \| `string`) => `void` | 19 | | `changePenWidth` | (`penwidth`: `undefined` \| `number`) => `void` | 20 | | `clear` | () => `void` | 21 | | `download` | (`opt`: `undefined` \| `DownloadOption`) => `void` | 22 | | `getSvgXML` | () => ``null`` \| `string` | 23 | | `undo` | () => `void` | 24 | 25 | #### Defined in 26 | 27 | [react/src/types.ts:4](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/react/src/types.ts#L4) 28 | 29 | ## Functions 30 | 31 | ### useSvgDrawing 32 | 33 | ▸ **useSvgDrawing**(`option?`): [`MutableRefObject`<``null`` \| `HTMLDivElement`\>, [`UseSvgDrawing`](svg_drawing_react.md#usesvgdrawing)] 34 | 35 | #### Parameters 36 | 37 | | Name | Type | 38 | | :------ | :------ | 39 | | `option?` | `Partial`<`DrawingOption`\> | 40 | 41 | #### Returns 42 | 43 | [`MutableRefObject`<``null`` \| `HTMLDivElement`\>, [`UseSvgDrawing`](svg_drawing_react.md#usesvgdrawing)] 44 | 45 | #### Defined in 46 | 47 | [react/src/useDrawing.ts:7](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/react/src/useDrawing.ts#L7) 48 | -------------------------------------------------------------------------------- /packages/animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@svg-drawing/animation", 3 | "version": "4.2.5", 4 | "license": "MIT", 5 | "description": "svg drawing library.", 6 | "author": { 7 | "name": "Kazuto Kamei", 8 | "email": "kmkzt0927@gmail.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/kmkzt/svg-drawing.git", 13 | "directory": "packages/animation" 14 | }, 15 | "homepage": "https://github.com/kmkzt/svg-drawing/tree/master/packages/animation#readme", 16 | "bugs": { 17 | "url": "https://github.com/kmkzt/svg-drawing/issues" 18 | }, 19 | "keywords": [ 20 | "svg", 21 | "drawing", 22 | "animation" 23 | ], 24 | "type": "module", 25 | "types": "./lib/index.d.ts", 26 | "main": "./lib/index.js", 27 | "browser": "./lib/index.umd.js", 28 | "exports": { 29 | "import": "./lib/index.js", 30 | "require": "./lib/index.cjs", 31 | "development": "./lib/index.dev.js" 32 | }, 33 | "files": [ 34 | "lib/**/*" 35 | ], 36 | "typedocMain": "src/index.ts", 37 | "scripts": { 38 | "dev": "NODE_ENV=development rollup -w -c", 39 | "dev:tsc": "NODE_ENV=development tsc -w", 40 | "build": "npm-run-all -p lib:*", 41 | "lib:rollup": "NODE_ENV=production rollup -c", 42 | "lib:tsc": "NODE_ENV=production tsc -p ./tsconfig.lib.json", 43 | "clear": "rimraf lib/* *.tsbuildinfo", 44 | "typecheck": "tsc", 45 | "lint": "eslint ./src --ext .js,.ts,.tsx", 46 | "fmt": "yarn lint --fix", 47 | "prepare": "yarn build" 48 | }, 49 | "optionalDependencies": { 50 | "@svg-drawing/core": "4.2.5" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "gitHead": "6811d0236e51e0204ffbf64a6124ca5469079373" 56 | } 57 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@svg-drawing/react", 3 | "version": "4.2.5", 4 | "description": "React svg drawing library. This library is a React extension of svg-drawing.", 5 | "author": { 6 | "name": "Kazuto Kamei", 7 | "email": "kmkzt0927@gmail.com" 8 | }, 9 | "homepage": "https://github.com/kmkzt/svg-drawing/tree/master/packages/react#readme", 10 | "license": "MIT", 11 | "type": "module", 12 | "types": "./lib/index.d.ts", 13 | "main": "./lib/index.js", 14 | "browser": "./lib/index.umd.js", 15 | "exports": { 16 | "import": "./lib/index.js", 17 | "require": "./lib/index.cjs", 18 | "development": "./lib/index.dev.js" 19 | }, 20 | "files": [ 21 | "lib/**/*" 22 | ], 23 | "typedocMain": "src/index.ts", 24 | "publishConfig": { 25 | "access": "public" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/kmkzt/svg-drawing.git", 30 | "directory": "packages/react" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/kmkzt/svg-drawing/issues" 34 | }, 35 | "scripts": { 36 | "dev": "NODE_ENV=development rollup -w -c", 37 | "dev:tsc": "NODE_ENV=development tsc -w", 38 | "build": "npm-run-all -p lib:*", 39 | "lib:rollup": "NODE_ENV=production rollup -c", 40 | "lib:tsc": "NODE_ENV=production tsc -p ./tsconfig.lib.json", 41 | "clear": "rimraf lib/* *.tsbuildinfo", 42 | "typecheck": "tsc", 43 | "lint": "eslint ./src --ext .js,.ts,.tsx", 44 | "fmt": "yarn lint --fix", 45 | "prepare": "yarn build" 46 | }, 47 | "peerDependencies": { 48 | "react": ">= 16.8.0" 49 | }, 50 | "optionalDependencies": { 51 | "@svg-drawing/core": "4.2.5", 52 | "@svg-drawing/img-trace": "4.2.5" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_core/ResizeHandler.md: -------------------------------------------------------------------------------- 1 | # Class: ResizeHandler 2 | 3 | [@svg-drawing/core](../../modules/svg_drawing_core.md).ResizeHandler 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new ResizeHandler**(`_el`, `__namedParameters`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `_el` | `HTMLElement` | 16 | | `__namedParameters` | [`ResizeHandlerCallback`](../../modules/svg_drawing_core.md#resizehandlercallback) | 17 | 18 | #### Defined in 19 | 20 | [core/src/handler.ts:183](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/handler.ts#L183) 21 | 22 | ## Properties 23 | 24 | ### resize 25 | 26 | • **resize**: (`rect`: `DOMRect` \| { `height`: `number` ; `left`: `number` ; `top`: `number` ; `width`: `number` }) => `void` 27 | 28 | #### Type declaration 29 | 30 | ▸ (`rect`): `void` 31 | 32 | ##### Parameters 33 | 34 | | Name | Type | 35 | | :------ | :------ | 36 | | `rect` | `DOMRect` \| { `height`: `number` ; `left`: `number` ; `top`: `number` ; `width`: `number` } | 37 | 38 | ##### Returns 39 | 40 | `void` 41 | 42 | #### Defined in 43 | 44 | [core/src/handler.ts:182](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/handler.ts#L182) 45 | 46 | ## Methods 47 | 48 | ### off 49 | 50 | ▸ **off**(): `void` 51 | 52 | #### Returns 53 | 54 | `void` 55 | 56 | #### Defined in 57 | 58 | [core/src/handler.ts:188](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/handler.ts#L188) 59 | 60 | ___ 61 | 62 | ### on 63 | 64 | ▸ **on**(): `void` 65 | 66 | #### Returns 67 | 68 | `void` 69 | 70 | #### Defined in 71 | 72 | [core/src/handler.ts:192](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/handler.ts#L192) 73 | -------------------------------------------------------------------------------- /packages/img-trace/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@svg-drawing/img-trace", 3 | "version": "4.2.5", 4 | "license": "MIT", 5 | "description": "svg drawing library.", 6 | "author": { 7 | "name": "Kazuto Kamei", 8 | "email": "kmkzt0927@gmail.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/kmkzt/svg-drawing.git", 13 | "directory": "packages/img-trace" 14 | }, 15 | "homepage": "https://github.com/kmkzt/svg-drawing/tree/master/packages/img-trace#readme", 16 | "bugs": { 17 | "url": "https://github.com/kmkzt/svg-drawing/issues" 18 | }, 19 | "keywords": [ 20 | "svg", 21 | "jpg", 22 | "png", 23 | "tracer" 24 | ], 25 | "type": "module", 26 | "types": "./lib/index.d.ts", 27 | "main": "./lib/index.js", 28 | "browser": "./lib/index.umd.js", 29 | "exports": { 30 | "import": "./lib/index.js", 31 | "require": "./lib/index.cjs", 32 | "development": "./lib/index.dev.js" 33 | }, 34 | "files": [ 35 | "lib/**/*" 36 | ], 37 | "typedocMain": "src/index.ts", 38 | "scripts": { 39 | "dev": "NODE_ENV=development rollup -w -c", 40 | "dev:tsc": "NODE_ENV=development tsc -w", 41 | "build": "npm-run-all -p lib:*", 42 | "lib:rollup": "NODE_ENV=production rollup -c", 43 | "lib:tsc": "NODE_ENV=production tsc -p ./tsconfig.lib.json", 44 | "clear": "rimraf lib/* *.tsbuildinfo", 45 | "typecheck": "tsc", 46 | "lint": "eslint ./src --ext .js,.ts,.tsx", 47 | "fmt": "yarn lint --fix", 48 | "prepare": "yarn build" 49 | }, 50 | "optionalDependencies": { 51 | "@svg-drawing/animation": "4.2.5", 52 | "@svg-drawing/core": "4.2.5" 53 | }, 54 | "publishConfig": { 55 | "access": "public" 56 | }, 57 | "gitHead": "6811d0236e51e0204ffbf64a6124ca5469079373" 58 | } 59 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_img_trace/ImgLoader.md: -------------------------------------------------------------------------------- 1 | # Class: ImgLoader 2 | 3 | [@svg-drawing/img-trace](../../modules/svg_drawing_img_trace.md).ImgLoader 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new ImgLoader**(`options`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `options` | `Partial`<`ImgLoaderOption`\> | 16 | 17 | #### Defined in 18 | 19 | [img-trace/src/imgloader.ts:8](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/imgloader.ts#L8) 20 | 21 | ## Properties 22 | 23 | ### corsenabled 24 | 25 | • **corsenabled**: `boolean` 26 | 27 | #### Defined in 28 | 29 | [img-trace/src/imgloader.ts:6](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/imgloader.ts#L6) 30 | 31 | ## Methods 32 | 33 | ### fromImageElement 34 | 35 | ▸ **fromImageElement**(`img`, `callback?`): `void` \| `Promise`<`ImageData`\> 36 | 37 | #### Parameters 38 | 39 | | Name | Type | 40 | | :------ | :------ | 41 | | `img` | `HTMLImageElement` | 42 | | `callback?` | (`imgd`: `ImageData`) => `void` | 43 | 44 | #### Returns 45 | 46 | `void` \| `Promise`<`ImageData`\> 47 | 48 | #### Defined in 49 | 50 | [img-trace/src/imgloader.ts:65](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/imgloader.ts#L65) 51 | 52 | ___ 53 | 54 | ### fromUrl 55 | 56 | ▸ **fromUrl**(`url`, `callback?`): `void` \| `Promise`<`ImageData`\> 57 | 58 | #### Parameters 59 | 60 | | Name | Type | 61 | | :------ | :------ | 62 | | `url` | `string` | 63 | | `callback?` | (`imgd`: `ImageData`) => `void` | 64 | 65 | #### Returns 66 | 67 | `void` \| `Promise`<`ImageData`\> 68 | 69 | #### Defined in 70 | 71 | [img-trace/src/imgloader.ts:14](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/imgloader.ts#L14) 72 | -------------------------------------------------------------------------------- /docs/packages/interfaces/svg_drawing_img_trace/ImgTraceOption.md: -------------------------------------------------------------------------------- 1 | # Interface: ImgTraceOption 2 | 3 | [@svg-drawing/img-trace](../../modules/svg_drawing_img_trace.md).ImgTraceOption 4 | 5 | ## Properties 6 | 7 | ### commandOmit 8 | 9 | • `Optional` **commandOmit**: `number` 10 | 11 | #### Defined in 12 | 13 | [img-trace/src/trace.ts:70](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/trace.ts#L70) 14 | 15 | ___ 16 | 17 | ### ltres 18 | 19 | • `Optional` **ltres**: `number` 20 | 21 | #### Defined in 22 | 23 | [img-trace/src/trace.ts:65](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/trace.ts#L65) 24 | 25 | ___ 26 | 27 | ### palettes 28 | 29 | • `Optional` **palettes**: [`Rgba`](Rgba.md)[] 30 | 31 | #### Defined in 32 | 33 | [img-trace/src/trace.ts:73](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/trace.ts#L73) 34 | 35 | ___ 36 | 37 | ### pathAttrs 38 | 39 | • `Optional` **pathAttrs**: `PathObject` 40 | 41 | #### Defined in 42 | 43 | [img-trace/src/trace.ts:72](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/trace.ts#L72) 44 | 45 | ___ 46 | 47 | ### pathOmit 48 | 49 | • `Optional` **pathOmit**: `number` 50 | 51 | #### Defined in 52 | 53 | [img-trace/src/trace.ts:69](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/trace.ts#L69) 54 | 55 | ___ 56 | 57 | ### qtres 58 | 59 | • `Optional` **qtres**: `number` 60 | 61 | #### Defined in 62 | 63 | [img-trace/src/trace.ts:66](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/trace.ts#L66) 64 | 65 | ___ 66 | 67 | ### rightangleenhance 68 | 69 | • `Optional` **rightangleenhance**: `boolean` 70 | 71 | #### Defined in 72 | 73 | [img-trace/src/trace.ts:67](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/trace.ts#L67) 74 | -------------------------------------------------------------------------------- /packages/animation/src/__snapshots__/animation.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`animation.ts SvgAnimation toAnimationElement 1`] = ` 4 | 11 | 18 | 25 | 32 | 33 | 40 | 47 | 54 | 55 | 56 | `; 57 | -------------------------------------------------------------------------------- /packages/img-trace/src/palette.test.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { loadPngData } from './__test__/loadPngData' 3 | import { Palette } from './palette' 4 | import type { Rgba } from './palette' 5 | 6 | const TEST_NUMBER_OF_COLORS = [undefined, 2, 4, 7, 8, 27, 64] 7 | const TEST_COLOR_QUANT_CYCLES = [undefined, 1, 8] 8 | const getTestResult = (pal: Rgba[]) => 9 | pal.map((c) => `rgba(${c.r}, ${c.g}, ${c.b}, ${c.a / 255})`) 10 | 11 | describe('palette.ts', () => { 12 | const testimage = resolve(__dirname, '__test__/panda.png') 13 | describe('Palette', () => { 14 | describe('Palette.imageData', () => { 15 | TEST_COLOR_QUANT_CYCLES.map((colorQuantCycles: number | undefined) => { 16 | TEST_NUMBER_OF_COLORS.map((numberOfColors: number | undefined) => { 17 | it(`colorQuantCycles: ${ 18 | colorQuantCycles || 'default' 19 | } ;numberOfColors: ${numberOfColors || 'default'}`, (done) => { 20 | loadPngData(testimage, (imgd: any) => { 21 | expect( 22 | getTestResult( 23 | Palette.imageData(imgd, { colorQuantCycles, numberOfColors }) 24 | ) 25 | ).toMatchSnapshot() 26 | done() 27 | }) 28 | }) 29 | }) 30 | }) 31 | }) 32 | describe('Palette.number', () => { 33 | // TODO: Rest colors 34 | ;[8, 27].map((arg: number | undefined) => { 35 | it(`numberOfColors: ${arg || 'default'}`, () => { 36 | expect(getTestResult(Palette.number(arg))).toMatchSnapshot() 37 | }) 38 | }) 39 | }) 40 | describe('Palette.gray', () => { 41 | ;[2, 7, 16].map((arg: number | undefined) => { 42 | it(`numberOfColors: ${arg || 'default'}`, () => { 43 | expect(getTestResult(Palette.grey(arg))).toMatchSnapshot() 44 | }) 45 | }) 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /packages/core/src/convert.ts: -------------------------------------------------------------------------------- 1 | import { Point, Command, COMMAND_TYPE } from './svg' 2 | import type { PointObject, ConvertOption } from './types' 3 | 4 | export class Convert { 5 | public ratio: number 6 | constructor({ ratio }: ConvertOption = {}) { 7 | this.ratio = ratio ?? 0.2 8 | } 9 | private _controlPoint( 10 | pr: PointObject, 11 | st: PointObject, 12 | ne: PointObject 13 | ): [number, number] { 14 | const prev = new Point(pr.x, pr.y) 15 | const start = new Point(st.x, st.y) 16 | const next = new Point(ne.x, ne.y) 17 | const vector = next.sub(prev).toVector().scale(this.ratio).toPoint() 18 | const po = start.add(vector) 19 | return [po.x, po.y] 20 | } 21 | 22 | public bezierCurve( 23 | p1: PointObject, 24 | p2: PointObject, 25 | p3: PointObject, 26 | p4: PointObject 27 | ): Command { 28 | const value = [ 29 | ...this._controlPoint(p1, p2, p3), 30 | ...this._controlPoint(p4, p3, p2), 31 | p3.x, 32 | p3.y, 33 | ] 34 | return new Command(COMMAND_TYPE.CURVE, value) 35 | } 36 | 37 | public lineCommands(points: PointObject[]): Command[] { 38 | return points.map( 39 | (p, i) => 40 | new Command(i === 0 ? COMMAND_TYPE.MOVE : COMMAND_TYPE.LINE, [p.x, p.y]) 41 | ) 42 | } 43 | 44 | public bezierCurveCommands(p: PointObject[]): Command[] { 45 | const commands: Command[] = [] 46 | if (p.length < 3) { 47 | return this.lineCommands(p) 48 | } 49 | for (let i = 0; i < p.length; i += 1) { 50 | if (i === 0) { 51 | commands.push(new Command(COMMAND_TYPE.MOVE, [p[i].x, p[i].y])) 52 | continue 53 | } 54 | commands.push( 55 | this.bezierCurve( 56 | p[i - 2 < 0 ? 0 : i - 2], 57 | p[i - 1], 58 | p[i], 59 | i < p.length - 1 ? p[i + 1] : p[i] 60 | ) 61 | ) 62 | } 63 | return commands 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_img_trace/Palette.md: -------------------------------------------------------------------------------- 1 | # Class: Palette 2 | 3 | [@svg-drawing/img-trace](../../modules/svg_drawing_img_trace.md).Palette 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new Palette**() 10 | 11 | ## Methods 12 | 13 | ### grey 14 | 15 | ▸ `Static` **grey**(`numberofcolors?`): [`Rgba`](../../interfaces/svg_drawing_img_trace/Rgba.md)[] 16 | 17 | #### Parameters 18 | 19 | | Name | Type | Default value | 20 | | :------ | :------ | :------ | 21 | | `numberofcolors` | `number` | `DEFAULT_NUMBER_OF_COLORS` | 22 | 23 | #### Returns 24 | 25 | [`Rgba`](../../interfaces/svg_drawing_img_trace/Rgba.md)[] 26 | 27 | #### Defined in 28 | 29 | [img-trace/src/palette.ts:152](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/palette.ts#L152) 30 | 31 | ___ 32 | 33 | ### imageData 34 | 35 | ▸ `Static` **imageData**(`argimgd`, `__namedParameters?`): [`Rgba`](../../interfaces/svg_drawing_img_trace/Rgba.md)[] 36 | 37 | #### Parameters 38 | 39 | | Name | Type | 40 | | :------ | :------ | 41 | | `argimgd` | `ImageData` | 42 | | `__namedParameters` | [`FromImageDataOptions`](../../interfaces/svg_drawing_img_trace/FromImageDataOptions.md) | 43 | 44 | #### Returns 45 | 46 | [`Rgba`](../../interfaces/svg_drawing_img_trace/Rgba.md)[] 47 | 48 | #### Defined in 49 | 50 | [img-trace/src/palette.ts:17](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/palette.ts#L17) 51 | 52 | ___ 53 | 54 | ### number 55 | 56 | ▸ `Static` **number**(`numberofcolors?`): [`Rgba`](../../interfaces/svg_drawing_img_trace/Rgba.md)[] 57 | 58 | #### Parameters 59 | 60 | | Name | Type | Default value | 61 | | :------ | :------ | :------ | 62 | | `numberofcolors` | `number` | `DEFAULT_NUMBER_OF_COLORS` | 63 | 64 | #### Returns 65 | 66 | [`Rgba`](../../interfaces/svg_drawing_img_trace/Rgba.md)[] 67 | 68 | #### Defined in 69 | 70 | [img-trace/src/palette.ts:116](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/img-trace/src/palette.ts#L116) 71 | -------------------------------------------------------------------------------- /packages/img-trace/src/trace.test.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs' 2 | import { resolve } from 'path' 3 | import { svgObjectToElement } from '@svg-drawing/core' 4 | import { loadPngData } from './__test__/loadPngData' 5 | import { Palette } from './palette' 6 | import { ImgTrace } from './trace' 7 | import type { ImgTraceOption } from './trace' 8 | 9 | const testPattern: { 10 | [key: string]: Partial 11 | } = { 12 | default: {}, 13 | curvy: { rightangleenhance: false }, 14 | ltres: { ltres: 0.01 }, 15 | qtres: { qtres: 0.01 }, 16 | pathomit_20: { pathOmit: 20 }, 17 | commandOmit: { commandOmit: 3 }, 18 | palettes_custom: { 19 | palettes: [ 20 | { r: 0, g: 0, b: 100, a: 255 }, 21 | { r: 255, g: 255, b: 255, a: 255 }, 22 | ], 23 | }, 24 | } 25 | 26 | describe('trace.ts', () => { 27 | const testimage = resolve(__dirname, '__test__/panda.png') 28 | it('TestPattern', () => { 29 | // TestPattern 30 | expect(testPattern).toMatchSnapshot() 31 | }) 32 | describe('ImgTrace', () => { 33 | Object.entries(testPattern).map(([testname, testopts]) => { 34 | it(`${testname}`, (done) => { 35 | loadPngData(testimage, (png) => { 36 | const svg = new ImgTrace({ 37 | palettes: Palette.imageData(png as any), 38 | ...testopts, 39 | }).load(png as any) 40 | 41 | const data = svgObjectToElement(svg.toJson()).outerHTML 42 | /** DEBUG * */ 43 | if (process.env.DEBUG === 'debug') { 44 | writeFileSync( 45 | resolve(__dirname, `__debug__/${testname}-${Date.now()}.svg`), 46 | data 47 | ) 48 | } 49 | 50 | // TODO: visual regression 51 | 52 | // Effect Image 53 | // TODO: visual regression 54 | // expect(imgd).toMatchSnapshot() 55 | 56 | // SvgString 57 | expect(data).toMatchSnapshot() 58 | done() 59 | }) 60 | }) 61 | }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /packages/core/src/__snapshots__/drawing.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`drawing.ts SvgDrawing clear() 1`] = ` 4 | 11 | `; 12 | 13 | exports[`drawing.ts SvgDrawing close 1`] = ` 14 | 21 | 29 | 30 | `; 31 | 32 | exports[`drawing.ts SvgDrawing curve = false 1`] = ` 33 | 40 | 48 | 49 | `; 50 | 51 | exports[`drawing.ts SvgDrawing default 1`] = ` 52 | 59 | 67 | 68 | `; 69 | 70 | exports[`drawing.ts SvgDrawing undo() 1`] = ` 71 | 78 | 86 | 87 | `; 88 | -------------------------------------------------------------------------------- /examples/react/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { MarkGithub } from '@styled-icons/octicons/MarkGithub' 2 | import Head from 'next/head' 3 | import Link from 'next/link' 4 | import { Fragment } from 'react' 5 | import { Flex, Box, Text, Link as RELink } from 'rebass/styled-components' 6 | import { createGlobalStyle } from 'styled-components' 7 | 8 | const GlobalStyle = createGlobalStyle` 9 | body, * { 10 | margin: 0; 11 | box-sizing: border-box; 12 | } 13 | 14 | a { 15 | color: initial; 16 | text-decoration: initial; 17 | } 18 | ` 19 | 20 | const GlobalHeader = () => ( 21 | 22 | 23 | 24 | 25 | 26 | 33 | svg-drawing 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | drawing 42 | 43 | 44 | 45 | 46 | animation 47 | 48 | 49 | 50 | 51 | img-trace 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ) 63 | 64 | const Layout = ({ 65 | children, 66 | title = '', 67 | }: { 68 | title?: string 69 | children: React.ReactNode 70 | }) => { 71 | return ( 72 | 73 | 74 | {`svg-drawing ${title}`} 75 | 76 | 77 | 78 | 79 | {children} 80 | 81 | 82 | ) 83 | } 84 | 85 | export default Layout 86 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # `@svg-drawing/core` 2 | 3 | [![npm version](https://img.shields.io/npm/v/@svg-drawing/core/latest.svg)](https://www.npmjs.com/package/@svg-drawing/core) [![npm download](https://img.shields.io/npm/dm/@svg-drawing/core.svg)](https://www.npmjs.com/package/@svg-drawing/core) 4 | 5 | ## How to use 6 | 7 | ### npm 8 | 9 | Install 10 | 11 | ```shell 12 | yarn add @svg-drawing/core 13 | # or 14 | # npm i svg-drawing 15 | ``` 16 | 17 | Example code is [here](/examples/docs/pages/demo/drawing.tsx) 18 | 19 | This example renders the drawing area. 20 | 21 | ```javascript 22 | import { SvgDrawing } from '@svg-drawing/core' 23 | 24 | const el = document.createElement('div') 25 | 26 | // Drawing area will be resized to fit the rendering area 27 | el.setAttribute( 28 | 'style', 29 | ` 30 | border: 1px solid #ddd; 31 | width: 500px; 32 | height: 500px; 33 | ` 34 | ) 35 | document.body.appendChid(el) 36 | new SvgDrawing(el) 37 | ``` 38 | 39 | SvgDrawing methods. 40 | 41 | ```javascript 42 | const draw = new SvgDrawing(el) 43 | 44 | // change parameter. There are other changeable parameters like fill, close, curve, etc. 45 | draw.penColor = '#00b' 46 | draw.penWidth = 10 47 | 48 | // drawing deactivate 49 | draw.off() 50 | // drawing reactivate 51 | draw.on() 52 | 53 | // drawing all clear 54 | draw.clear() 55 | // undo drawing 56 | draw.undo() 57 | 58 | // Download image. Also available in SvgAnimation, Renderer 59 | draw.download() // Default download is svg. 60 | draw.download({ extension: 'jpg', filename: 'example.jpg' }) 61 | draw.download({ extension: 'png', filename: 'example.png' }) 62 | 63 | // Load svg data. Only the path element. 64 | // SVG exported by this library can be read. 65 | draw.svg.parseSVGString( 66 | '' 67 | ) 68 | draw.svg.parseSVGElement(document.getElementByID('loadSVG')) 69 | ``` 70 | 71 | ### CDN 72 | 73 | ```html 74 |
75 | 76 | 79 | ``` 80 | 81 | [Here](/example/html/) is an example for Html only. 82 | -------------------------------------------------------------------------------- /packages/core/src/drawing.test.ts: -------------------------------------------------------------------------------- 1 | import { SvgDrawing } from './drawing' 2 | import { svgObjectToElement } from './renderer' 3 | 4 | describe('drawing.ts', () => { 5 | describe('SvgDrawing', () => { 6 | it('default', () => { 7 | const draw: SvgDrawing = new SvgDrawing(document.createElement('div')) 8 | draw.drawStart() 9 | draw.drawMove({ x: 0, y: 0 }) 10 | draw.drawMove({ x: 1, y: 1 }) 11 | draw.drawMove({ x: 2, y: 1 }) 12 | draw.drawMove({ x: 3, y: 0 }) 13 | draw.drawEnd() 14 | expect(svgObjectToElement(draw.svg.toJson())).toMatchSnapshot() 15 | }) 16 | it('close', () => { 17 | const draw: SvgDrawing = new SvgDrawing(document.createElement('div')) 18 | draw.close = true 19 | draw.drawStart() 20 | draw.drawMove({ x: 0, y: 0 }) 21 | draw.drawMove({ x: 1, y: 1 }) 22 | draw.drawMove({ x: 2, y: 1 }) 23 | draw.drawMove({ x: 3, y: 0 }) 24 | draw.drawEnd() 25 | const el = svgObjectToElement(draw.svg.toJson()) 26 | expect(el).toMatchSnapshot() 27 | }) 28 | it('curve = false', () => { 29 | const draw: SvgDrawing = new SvgDrawing(document.createElement('div')) 30 | draw.curve = false 31 | draw.drawStart() 32 | draw.drawMove({ x: 0, y: 0 }) 33 | draw.drawMove({ x: 1, y: 1 }) 34 | draw.drawMove({ x: 2, y: 1 }) 35 | draw.drawMove({ x: 3, y: 0 }) 36 | draw.drawEnd() 37 | const el = svgObjectToElement(draw.svg.toJson()) 38 | expect(el).toMatchSnapshot() 39 | }) 40 | it('clear()', () => { 41 | const draw: SvgDrawing = new SvgDrawing(document.createElement('div')) 42 | draw.curve = false 43 | draw.drawStart() 44 | draw.drawMove({ x: 0, y: 0 }) 45 | draw.drawEnd() 46 | draw.clear() 47 | const el = svgObjectToElement(draw.svg.toJson()) 48 | expect(el).toMatchSnapshot() 49 | }) 50 | 51 | // TODO: Fix NaN 52 | it('undo()', () => { 53 | const draw: SvgDrawing = new SvgDrawing(document.createElement('div')) 54 | draw.drawStart() 55 | draw.drawMove({ x: 0, y: 0 }) 56 | draw.drawEnd() 57 | draw.drawStart() 58 | draw.drawMove({ x: 0, y: 0 }) 59 | draw.drawEnd() 60 | draw.undo() 61 | const el = svgObjectToElement(draw.svg.toJson()) 62 | expect(el).toMatchSnapshot() 63 | }) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /packages/animation/src/animation.test.ts: -------------------------------------------------------------------------------- 1 | import { SvgAnimation } from './animation' 2 | import type { FrameAnimation } from './types' 3 | 4 | const defaultTestData = ` 5 | 6 | 7 | ` 8 | 9 | describe('animation.ts', () => { 10 | describe('SvgAnimation', () => { 11 | const generateAnimation = (svgStr = defaultTestData) => { 12 | const anim = new SvgAnimation(document.createElement('div')) 13 | anim.svg.parseSVGString(svgStr) 14 | return anim 15 | } 16 | 17 | it('generateFrame', () => { 18 | const svg = generateAnimation() 19 | svg.setAnimation((paths) => { 20 | return [paths[0]] 21 | }) 22 | expect(svg.generateFrame().length).toBe(1) 23 | }) 24 | 25 | // TODO Improve test pattern 26 | it('toAnimationElement', () => { 27 | const svg = generateAnimation() 28 | const testAnimation: FrameAnimation = (paths, count: any) => { 29 | const update = [] 30 | for (let i = 0; i < paths.length; i += 1) { 31 | // Test property 32 | if (count % 2 === 0) paths[i].attrs.stroke = '#0ff' 33 | // Test Attribute 34 | if (count % 3 === 0) 35 | Object.assign(paths[i].attrs, { 36 | strokeLinecap: 'mitter', 37 | }) 38 | if (count < paths[i].commands.length) { 39 | paths[i].commands = paths[i].commands.slice(0, count) 40 | update.push(paths[i]) 41 | break 42 | } 43 | count -= paths[i].commands.length 44 | update.push(paths[i]) 45 | } 46 | return update 47 | } 48 | svg.setAnimation(testAnimation) 49 | expect(svg.toElement()).toMatchSnapshot() 50 | }) 51 | 52 | it('setAnimation, start, stop', async () => { 53 | const svg = generateAnimation() 54 | let loop = 0 55 | svg.setAnimation( 56 | (_paths, fid) => { 57 | loop += 1 58 | return [] 59 | }, 60 | { 61 | ms: 300, 62 | frames: 3, 63 | } 64 | ) 65 | svg.start() 66 | setTimeout(() => { 67 | svg.stop() 68 | expect(loop).toBe(3) 69 | }, 1000) 70 | }) 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /packages/img-trace/README.md: -------------------------------------------------------------------------------- 1 | # `@svg-drawing/img-trace` 2 | 3 | [![npm version](https://img.shields.io/npm/v/@svg-drawing/img-trace/latest.svg)](https://www.npmjs.com/package/@svg-drawing/img-trace) [![npm download](https://img.shields.io/npm/dm/@svg-drawing/img-trace.svg)](https://www.npmjs.com/package/@svg-drawing/img-trace) 4 | 5 | ## Install 6 | 7 | ### npm 8 | 9 | ```shell 10 | yarn add @svg-drawing/img-trace 11 | # or 12 | # npm i @svg-drawing/img-trace 13 | ``` 14 | 15 | ## How to use 16 | 17 | Example code is [here](/examples/docs/pages/demo/img-trace.tsx) 18 | 19 | Example of downloading the image converted to Svg 20 | 21 | ```ts 22 | import { 23 | ImgTrace 24 | ImgLoader, 25 | } from '@svg-drawing/img-trace' 26 | import { download } from '@svg-drawing/core' 27 | 28 | const svgDownload = async () => { 29 | new ImgLoader().fromUrl('./images/example.png', (imgd) => { 30 | const svg = new ImgTrace().load(imgd) 31 | download(svg) 32 | } 33 | } 34 | 35 | svgDownload() 36 | ``` 37 | 38 | Example of rendering an image converted to Svg 39 | 40 | ```ts 41 | import { Renderer } from '@svg-drawing/core' 42 | import { 43 | ImgTrace 44 | ImgLoader, 45 | } from '@svg-drawing/img-trace' 46 | 47 | const handleImage = (imgd) => { 48 | const svg = new ImgTrace().load(imgd) 49 | const renderer = new Renderer(document.getElementById('render-area')) 50 | renderer.update(svg.toJson()) 51 | } 52 | 53 | new ImgLoader().fromUrl('./images/example.png', handleImage) 54 | ``` 55 | 56 | Example of get colors palettes. 57 | 58 | ```ts 59 | import { 60 | ImgTrace 61 | ImgLoader, 62 | Palette 63 | } from '@svg-drawing/img-trace' 64 | import { download } from '@svg-drawing/core' 65 | 66 | // imgd is new ImageData() 67 | const colorSvgDownload = () => { 68 | new ImgLoader().fromUrl('./images/example.png', (imgd) => { 69 | // extracting colors from an image. 70 | const palette = Palette.imageData(imgd, { 71 | numberOfColors: 8 // The default value. If it is 8 or less, the value is gray scale. 72 | }) 73 | // const palette = Palette.number(8) // Extracts the color evenly by the number passed 74 | // const palette = Palette.grey(8) // Grey scale palettes. 75 | 76 | const svg = new ImgTrace({ palettes }).load(imgd) 77 | download(svg) 78 | }) 79 | } 80 | 81 | colorSvgDownload() 82 | 83 | ``` 84 | 85 | ## Thanks 86 | 87 | https://github.com/jankovicsandras/imagetracerjs 88 | -------------------------------------------------------------------------------- /examples/react/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Head, Html, Main, NextScript } from 'next/document' 2 | import { ServerStyleSheet } from 'styled-components' 3 | import { GA_TRACKING_ID } from '../lib/gtag' 4 | import type { DocumentContext, DocumentInitialProps } from 'next/document' 5 | 6 | export default class MyDocument extends Document { 7 | static async getInitialProps( 8 | ctx: DocumentContext 9 | ): Promise { 10 | const sheet = new ServerStyleSheet() 11 | const originalRenderPage = ctx.renderPage 12 | 13 | try { 14 | ctx.renderPage = () => 15 | originalRenderPage({ 16 | enhanceApp: (App) => (props) => 17 | sheet.collectStyles(), 18 | }) 19 | 20 | const initialProps = await Document.getInitialProps(ctx) 21 | return { 22 | html: initialProps.html, 23 | head: initialProps.head, 24 | // @ts-expect-error 25 | styles: ( 26 | <> 27 | {initialProps.styles} 28 | {sheet.getStyleElement()} 29 | 30 | ), 31 | } 32 | } catch { 33 | return { 34 | styles: undefined, 35 | html: '', 36 | head: undefined, 37 | } 38 | } finally { 39 | sheet.seal() 40 | } 41 | } 42 | 43 | render() { 44 | return ( 45 | 46 | 47 | 48 | 53 | {/* Global Site Tag (gtag.js) - Google Analytics */} 54 | 49 | 52 | ``` 53 | 54 | **[Here](./examples/html)** is an example for Html only. 55 | 56 | ## Packages 57 | 58 | | Packages | Description | 59 | | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | 60 | | [@svg-drawing/core![npm version](https://img.shields.io/npm/v/@svg-drawing/core/latest.svg)](./packages/core) | Core Module | 61 | | [@svg-drawing/animation![npm version](https://img.shields.io/npm/v/@svg-drawing/animation/latest.svg)](./packages/animation) | Animate the drawn Svg. Can be animations using `JavaScript` or `` | 62 | | [@svg-drawing/img-trace![npm version](https://img.shields.io/npm/v/@svg-drawing/img-trace/latest.svg)](./packages/img-trace) | Image(png/jpg) convert Svg. | 63 | | [@svg-drawing/react![npm version](https://img.shields.io/npm/v/@svg-drawing/react/latest.svg)](./packages/react) | For React. | 64 | -------------------------------------------------------------------------------- /packages/core/src/download.ts: -------------------------------------------------------------------------------- 1 | import { svgObjectToElement } from './renderer' 2 | import type { Svg } from './svg' 3 | import type { DownloadOption, SvgObject } from './types' 4 | 5 | export const toBase64 = (svgObj: SvgObject): string => { 6 | return svg2base64(svgObjectToElement(svgObj).outerHTML) 7 | } 8 | 9 | export const svg2base64 = (svg: string): string => 10 | `data:image/svg+xml;base64,${btoa(svg)}` 11 | 12 | export const mimeTypeMap: { [key in DownloadOption['extension']]: string } = { 13 | png: 'image/png', 14 | jpg: 'image/jpeg', 15 | svg: 'image/svg+xml', 16 | } as const 17 | 18 | export const downloadBlob = ({ 19 | data, 20 | extension, 21 | filename, 22 | }: { 23 | data: string 24 | extension: keyof typeof mimeTypeMap 25 | filename?: string 26 | }): void => { 27 | const bin = atob(data.replace(/^.*,/, '')) 28 | const buffer = new Uint8Array(bin.length) 29 | for (let i = 0; i < bin.length; i += 1) { 30 | buffer[i] = bin.charCodeAt(i) 31 | } 32 | const fname = filename || `${Date.now()}.${extension}` 33 | const blob = new Blob([buffer.buffer], { 34 | type: mimeTypeMap[extension], 35 | }) 36 | if ((window.navigator as any).msSaveBlob) { 37 | // IE 38 | ;(window.navigator as any).msSaveBlob(blob, fname) 39 | } else if (window.URL && window.URL.createObjectURL) { 40 | // Firefox, Chrome, Safari 41 | const a = document.createElement('a') 42 | a.download = fname 43 | a.href = window.URL.createObjectURL(blob) 44 | document.body.appendChild(a) 45 | a.click() 46 | document.body.removeChild(a) 47 | } else { 48 | // Other 49 | window.open(data, '_blank') 50 | } 51 | } 52 | 53 | const defaultOpts: DownloadOption = { 54 | extension: 'svg', 55 | } 56 | // TODO: Add filename config 57 | export const download = ( 58 | svg: Svg, 59 | opt: DownloadOption = defaultOpts, 60 | dlb: typeof downloadBlob = downloadBlob 61 | ): void => { 62 | const { filename, extension: ext } = { ...defaultOpts, ...opt } 63 | const base64 = toBase64(svg.toJson()) 64 | if (ext === 'svg') { 65 | dlb({ 66 | data: base64, 67 | extension: 'svg', 68 | filename, 69 | }) 70 | return 71 | } 72 | 73 | const { width, height, background } = svg 74 | const img: any = new Image() 75 | const renderCanvas = () => { 76 | const canvas = document.createElement('canvas') 77 | canvas.setAttribute('width', String(width)) 78 | canvas.setAttribute('height', String(height)) 79 | const ctx = canvas.getContext('2d') 80 | if (!ctx) return 81 | if (background || ext === 'jpg') { 82 | ctx.fillStyle = background || '#fff' 83 | ctx.fillRect(0, 0, width, height) 84 | } 85 | ctx.drawImage(img, 0, 0) 86 | if (ext === 'png') { 87 | dlb({ data: canvas.toDataURL('image/png'), extension: 'png' }) 88 | } else { 89 | dlb({ data: canvas.toDataURL('image/jpeg'), extension: 'jpg' }) 90 | } 91 | } 92 | img.addEventListener('load', renderCanvas, false) 93 | img.src = base64 94 | } 95 | -------------------------------------------------------------------------------- /examples/react/config/theme.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/rebassjs/rebass/blob/master/packages/preset/src/index.js 2 | export default { 3 | colors: { 4 | text: '#000', 5 | background: '#fff', 6 | primary: '#07c', 7 | secondary: '#30c', 8 | muted: '#f6f6f9', 9 | gray: '#dddddf', 10 | highlight: 'hsla(205, 100%, 40%, 0.125)', 11 | }, 12 | fonts: { 13 | body: 'system-ui, sans-serif', 14 | heading: 'inherit', 15 | monospace: 'Menlo, monospace', 16 | }, 17 | fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 96], 18 | fontWeights: { 19 | body: 400, 20 | heading: 700, 21 | bold: 700, 22 | }, 23 | lineHeights: { 24 | body: 1.5, 25 | heading: 1.25, 26 | }, 27 | space: [0, 4, 8, 16, 32, 64, 128, 256, 512], 28 | sizes: { 29 | avatar: 48, 30 | }, 31 | radii: { 32 | default: 4, 33 | circle: 99999, 34 | }, 35 | shadows: { 36 | card: '0 0 4px rgba(0, 0, 0, .125)', 37 | }, 38 | // rebass variants 39 | text: { 40 | heading: { 41 | fontFamily: 'heading', 42 | lineHeight: 'heading', 43 | fontWeight: 'heading', 44 | }, 45 | display: { 46 | fontFamily: 'heading', 47 | fontWeight: 'heading', 48 | lineHeight: 'heading', 49 | fontSize: [5, 6, 7], 50 | }, 51 | caps: { 52 | textTransform: 'uppercase', 53 | letterSpacing: '0.1em', 54 | }, 55 | }, 56 | variants: { 57 | avatar: { 58 | width: 'avatar', 59 | height: 'avatar', 60 | borderRadius: 'circle', 61 | }, 62 | card: { 63 | p: 2, 64 | bg: 'background', 65 | boxShadow: 'card', 66 | }, 67 | link: { 68 | color: 'primary', 69 | }, 70 | nav: { 71 | fontSize: 1, 72 | fontWeight: 'bold', 73 | display: 'inline-block', 74 | p: 2, 75 | color: 'inherit', 76 | textDecoration: 'none', 77 | ':hover,:focus,.active': { 78 | color: 'primary', 79 | }, 80 | }, 81 | }, 82 | buttons: { 83 | primary: { 84 | fontSize: 1, 85 | fontWeight: 'bold', 86 | color: 'background', 87 | bg: 'primary', 88 | borderRadius: 'default', 89 | }, 90 | outline: { 91 | variant: 'buttons.default', 92 | color: 'primary', 93 | bg: 'transparent', 94 | boxShadow: 'inset 0 0 2px', 95 | }, 96 | secondary: { 97 | variant: 'buttons.default', 98 | color: 'background', 99 | bg: 'secondary', 100 | }, 101 | }, 102 | styles: { 103 | root: { 104 | fontFamily: 'body', 105 | fontWeight: 'body', 106 | lineHeight: 'body', 107 | }, 108 | }, 109 | // https://rebassjs.org/forms/#theming 110 | forms: { 111 | input: { 112 | p: 1, 113 | }, 114 | select: { 115 | borderRadius: 9999, 116 | }, 117 | textarea: {}, 118 | label: { 119 | width: 'auto', 120 | fontSize: 0, 121 | }, 122 | radio: {}, 123 | checkbox: {}, 124 | }, 125 | } 126 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_core/Point.md: -------------------------------------------------------------------------------- 1 | # Class: Point 2 | 3 | [@svg-drawing/core](../../modules/svg_drawing_core.md).Point 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new Point**(`x`, `y`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `x` | `number` | 16 | | `y` | `number` | 17 | 18 | #### Defined in 19 | 20 | [core/src/svg.ts:7](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L7) 21 | 22 | ## Properties 23 | 24 | ### x 25 | 26 | • **x**: `number` 27 | 28 | #### Defined in 29 | 30 | [core/src/svg.ts:5](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L5) 31 | 32 | ___ 33 | 34 | ### y 35 | 36 | • **y**: `number` 37 | 38 | #### Defined in 39 | 40 | [core/src/svg.ts:6](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L6) 41 | 42 | ## Methods 43 | 44 | ### add 45 | 46 | ▸ **add**(`p`): [`Point`](Point.md) 47 | 48 | #### Parameters 49 | 50 | | Name | Type | 51 | | :------ | :------ | 52 | | `p` | [`Point`](Point.md) | 53 | 54 | #### Returns 55 | 56 | [`Point`](Point.md) 57 | 58 | #### Defined in 59 | 60 | [core/src/svg.ts:22](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L22) 61 | 62 | ___ 63 | 64 | ### clone 65 | 66 | ▸ **clone**(): [`Point`](Point.md) 67 | 68 | #### Returns 69 | 70 | [`Point`](Point.md) 71 | 72 | #### Defined in 73 | 74 | [core/src/svg.ts:34](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L34) 75 | 76 | ___ 77 | 78 | ### eql 79 | 80 | ▸ **eql**(`p`): `boolean` 81 | 82 | #### Parameters 83 | 84 | | Name | Type | 85 | | :------ | :------ | 86 | | `p` | [`Point`](Point.md) | 87 | 88 | #### Returns 89 | 90 | `boolean` 91 | 92 | #### Defined in 93 | 94 | [core/src/svg.ts:30](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L30) 95 | 96 | ___ 97 | 98 | ### scale 99 | 100 | ▸ **scale**(`r`): [`Point`](Point.md) 101 | 102 | #### Parameters 103 | 104 | | Name | Type | 105 | | :------ | :------ | 106 | | `r` | `number` | 107 | 108 | #### Returns 109 | 110 | [`Point`](Point.md) 111 | 112 | #### Defined in 113 | 114 | [core/src/svg.ts:18](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L18) 115 | 116 | ___ 117 | 118 | ### sub 119 | 120 | ▸ **sub**(`p`): [`Point`](Point.md) 121 | 122 | #### Parameters 123 | 124 | | Name | Type | 125 | | :------ | :------ | 126 | | `p` | [`Point`](Point.md) | 127 | 128 | #### Returns 129 | 130 | [`Point`](Point.md) 131 | 132 | #### Defined in 133 | 134 | [core/src/svg.ts:26](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L26) 135 | 136 | ___ 137 | 138 | ### toVector 139 | 140 | ▸ **toVector**(): [`Vector`](Vector.md) 141 | 142 | #### Returns 143 | 144 | [`Vector`](Vector.md) 145 | 146 | #### Defined in 147 | 148 | [core/src/svg.ts:12](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L12) 149 | -------------------------------------------------------------------------------- /packages/img-trace/src/imgloader.ts: -------------------------------------------------------------------------------- 1 | interface ImgLoaderOption { 2 | corsenabled: boolean 3 | } 4 | 5 | export class ImgLoader { 6 | public corsenabled: boolean 7 | 8 | constructor(options: Partial) { 9 | this.corsenabled = options.corsenabled ?? true 10 | } 11 | 12 | // TODO: improve types. 13 | // if exists callback, return void. if not existed callback, return Promise. 14 | public fromUrl( 15 | url: string, 16 | callback?: (imgd: ImageData) => void 17 | ): void | Promise { 18 | // TODO: cors improve 19 | // const xhr = new XMLHttpRequest() 20 | // xhr.responseType = 'blob' 21 | // xhr.open('GET', url, true) 22 | // xhr.onload = function() { 23 | // const img = new Image() 24 | // img.onload = function() { 25 | // this.loadImageElement(img, callback) 26 | // } 27 | // img.onerror = (err: any) => { 28 | // console.log(err) 29 | // } 30 | // console.log(this.response) 31 | 32 | // img.src = URL.createObjectURL(this.response) 33 | // } 34 | // xhr.onerror = (err: any) => console.log(err) 35 | // xhr.send(null) 36 | const load = ( 37 | resolve: (imgd: ImageData) => void, 38 | reject?: (a: any) => void 39 | ) => { 40 | const img = new Image() 41 | if (this.corsenabled) { 42 | img.crossOrigin = 'Anonymous' 43 | } 44 | img.onload = () => { 45 | this.fromImageElement(img, resolve) 46 | } 47 | img.onerror = (err: any) => { 48 | if (reject) { 49 | reject(err) 50 | } else { 51 | console.error(err) 52 | } 53 | } 54 | img.src = url 55 | } 56 | if (callback) { 57 | load(callback) 58 | } else { 59 | return new Promise(load) 60 | } 61 | } 62 | 63 | // TODO: improve types. 64 | // if exists callback, return void. if not existed callback, return Promise. 65 | public fromImageElement( 66 | img: HTMLImageElement, 67 | callback?: (imgd: ImageData) => void 68 | ): Promise | void { 69 | const load = ( 70 | resolve: (imgd: ImageData) => void, 71 | reject?: (a: any) => void 72 | ) => { 73 | const canvas = document.createElement('canvas') 74 | canvas.width = img.naturalWidth || img.width 75 | canvas.height = img.naturalHeight || img.height 76 | const context = canvas.getContext('2d') 77 | context?.drawImage(img, 0, 0) 78 | const imgd: ImageData | undefined = context?.getImageData( 79 | 0, 80 | 0, 81 | canvas.width, 82 | canvas.height 83 | ) 84 | if (!imgd) { 85 | if (reject) { 86 | reject('error canvas context.') 87 | return 88 | } else { 89 | throw 'error canvas context.' 90 | } 91 | } 92 | resolve(imgd) 93 | } 94 | if (callback) { 95 | load(callback) 96 | } else { 97 | return new Promise(load) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /packages/react/README.md: -------------------------------------------------------------------------------- 1 | # `@svg-drawing/react` 2 | 3 | [![npm version](https://img.shields.io/npm/v/@svg-drawing/react/latest.svg)](https://www.npmjs.com/package/@svg-drawing/react) [![npm download](https://img.shields.io/npm/dm/@svg-drawing/react.svg)](https://www.npmjs.com/package/@svg-drawing/react) 4 | 5 | ## Get started 6 | 7 | ```shell 8 | yarn add react @svg-drawing/react 9 | ``` 10 | 11 | ## How to use 12 | 13 | Example code is [here](/examples/demo/pages/react.tsx) 14 | 15 | This is example. 16 | 17 | ```javascript 18 | import React from 'react' 19 | import { useSvgDrawing } from '@svg-drawing/react' 20 | 21 | const Drawing = () => { 22 | const [renderRef, draw] = useSvgDrawing() 23 | // Drawing area will be resized to fit the rendering area 24 | return
25 | } 26 | ``` 27 | 28 | useSvgDrawing options. 29 | 30 | ```javascript 31 | const [renderRef, draw] = useSvgDrawing({ 32 | penWidth: 10, // pen width 33 | penColor: '#e00', // pen color 34 | close: true, // Use close command for path. Default is false. 35 | curve: false, // Use curve command for path. Default is true. 36 | delay: 60, // Set how many ms to draw points every. 37 | fill: '', // Set fill attribute for path. default is `none` 38 | }) 39 | ``` 40 | 41 | Drawing methods. 42 | 43 | ```javascript 44 | // svg-drawing hooks 45 | const [renderRef, draw] = useSvgDrawing() 46 | 47 | // Call the SvgDrawing. Access the current settings of penWidth, penColor etc 48 | // Details are https://github.com/kmkzt/svg-drawing/tree/master/packages/core. 49 | const logger = useCallback(() => { 50 | if (!draw.ref.current) return 51 | console.log(draw.ref.current.penColor) // #333 52 | console.log(draw.ref.current.penWidth) // 1 53 | }, []) 54 | 55 | // Erase all drawing. 56 | return 57 | // Undo drawing. 58 | return 59 | 60 | // Download image. 61 | const handleDownload = useCallback(() => { 62 | draw.download() // default svg download 63 | draw.download({ extension: 'svg', filename: 'a.svg'}) 64 | draw.download({ extension: 'png', filename: 'b.png'}) 65 | draw.download({ extension: 'jpg', filename: 'c.jpg'}) 66 | }, [draw.download]) 67 | 68 | // Chage parameter 69 | const handleChangeParameter = useCallback(() => { 70 | // Change pen config 71 | draw.changePenColor('#00b') 72 | // Change pen width 73 | draw.changePenWidth(10) 74 | // Change fill attribure of svg path element. 75 | draw.changFill('#00b') 76 | // Change throttle delay of drawing 77 | draw.changeDelay(10) 78 | // Set whether to use curved comma for svg path element. 79 | draw.changCurve(false) 80 | // Set whether to use curved comma for svg path element. 81 | draw.changeClose(true) 82 | }, [draw]) 83 | 84 | // get svgXML 85 | // return SVGElement 86 | const loggerXML = useCallback(() => { 87 | console.log(draw.getSvgXML()) // 4 |
5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 24 |
25 | 26 | 27 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /config/rollup/build.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs' 2 | import nodeResolve from '@rollup/plugin-node-resolve' 3 | import replace from '@rollup/plugin-replace' 4 | import babel from 'rollup-plugin-babel' 5 | import sourceMaps from 'rollup-plugin-sourcemaps' 6 | import { terser } from 'rollup-plugin-terser' 7 | import { createBanner } from './banner' 8 | import { packageGlobals } from './globals' 9 | 10 | const extensions = ['.js', '.jsx', '.ts', '.tsx', '.json'] 11 | const targets = '>1%, not dead, not ie 11, not op_mini all' 12 | 13 | export default ({ getBabelOptions, globals: injectGlobal }) => 14 | ({ input, pkg }) => { 15 | const globals = { 16 | ...(injectGlobal || {}), 17 | ...packageGlobals, 18 | } 19 | const external = Object.keys({ 20 | ...globals, 21 | ...(pkg.peerDependencies || {}), 22 | ...(pkg.optionalDependencies || {}), 23 | }) 24 | 25 | const banner = createBanner({ 26 | name: pkg.name, 27 | version: pkg.version, 28 | license: pkg.license, 29 | author: pkg.author.name, 30 | }) 31 | 32 | const buildPlugins = (opts) => [ 33 | commonjs(), 34 | babel( 35 | getBabelOptions({ 36 | esm: opts.esm, 37 | extensions, 38 | ...(opts.esm ? { targets } : undefined), 39 | }) 40 | ), 41 | nodeResolve({ extensions }), 42 | sourceMaps(), 43 | ] 44 | 45 | const optimizePlugin = (opts) => [ 46 | replace({ 47 | 'process.env.NODE_ENV': JSON.stringify(opts.mode), 48 | preventAssignment: true, 49 | }), 50 | terser({ output: { comments: /Copyright/i } }), 51 | ] 52 | 53 | return [ 54 | // umd 55 | { 56 | input, 57 | output: { 58 | file: pkg.browser, 59 | format: 'umd', 60 | name: pkg.name, 61 | globals, 62 | exports: 'named', 63 | sourcemap: true, 64 | banner, 65 | }, 66 | external, 67 | plugins: [ 68 | ...buildPlugins({ esm: false }), 69 | ...optimizePlugin({ mode: 'production' }), 70 | ], 71 | }, 72 | // cjs 73 | { 74 | input, 75 | output: { 76 | file: pkg.exports.require, 77 | format: 'cjs', 78 | exports: 'named', 79 | sourcemap: true, 80 | banner, 81 | }, 82 | external, 83 | plugins: [ 84 | ...buildPlugins({ esm: false }), 85 | ...optimizePlugin('production'), 86 | ], 87 | }, 88 | // esm 89 | { 90 | input, 91 | output: { 92 | file: pkg.exports.import, 93 | format: 'esm', 94 | sourcemap: true, 95 | banner, 96 | }, 97 | external, 98 | plugins: [ 99 | ...buildPlugins({ esm: true }), 100 | ...optimizePlugin('production'), 101 | ], 102 | }, 103 | // esm(development) 104 | { 105 | input, 106 | output: { 107 | file: pkg.exports.development, 108 | format: 'esm', 109 | sourcemap: true, 110 | banner, 111 | }, 112 | external, 113 | plugins: [ 114 | ...buildPlugins({ esm: true }), 115 | ...optimizePlugin('development'), 116 | ], 117 | }, 118 | ] 119 | } 120 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@svg-drawing/root", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*", 6 | "examples/*", 7 | "generate-docs" 8 | ], 9 | "scripts": { 10 | "build": "yarn clear; turbo run build --filter=@svg-drawing*", 11 | "build:gh-page": "export BASE_PATH=\"/svg-drawing\"; yarn build", 12 | "clear": "turbo run clear --filter=@svg-drawing*", 13 | "generate:docs": "yarn build; turbo run build --filter=svg-drawing-generate-docs", 14 | "dev": "concurrently yarn:dev:{main,tsc}", 15 | "dev:main": "turbo run dev --parallel --no-cache", 16 | "dev:tsc": "turbo run dev:tsc --parallel --no-cache", 17 | "dev:test": "DEBUG=debug yarn test --watchAll", 18 | "test": "NODE_ENV=test jest --ci --passWithNoTests", 19 | "lint": "turbo run lint", 20 | "fmt": "turbo run fmt", 21 | "lint-fix": "eslint . --ext ts,tsx,js,jsx --fix", 22 | "typecheck": "turbo run typecheck", 23 | "versionup": "lerna version --no-push --no-git-tag-version --exact", 24 | "release": "yarn build; lerna publish from-package", 25 | "release:dryrun": "yarn build; lerna publish from-package --no-push --registry=http://localhost:4873/" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "7.20.12", 29 | "@babel/plugin-transform-runtime": "7.19.6", 30 | "@babel/preset-env": "7.20.2", 31 | "@babel/preset-react": "7.18.6", 32 | "@babel/preset-typescript": "7.18.6", 33 | "@lerna-lite/cli": "1.15.1", 34 | "@rollup/plugin-commonjs": "22.0.1", 35 | "@rollup/plugin-json": "4.1.0", 36 | "@rollup/plugin-node-resolve": "13.3.0", 37 | "@rollup/plugin-replace": "4.0.0", 38 | "@testing-library/react": "13.4.0", 39 | "@testing-library/react-hooks": "8.0.1", 40 | "@testing-library/user-event": "14.4.3", 41 | "@types/jest": "28.1.8", 42 | "@types/node": "16.18.13", 43 | "@types/react": "18.0.14", 44 | "@types/react-dom": "18.0.5", 45 | "@typescript-eslint/eslint-plugin": "5.8.1", 46 | "@typescript-eslint/parser": "5.8.1", 47 | "@typescript-eslint/typescript-estree": "5.8.1", 48 | "babel-jest": "28.1.3", 49 | "babel-plugin-annotate-pure-calls": "0.4.0", 50 | "babel-plugin-transform-react-remove-prop-types": "0.4.24", 51 | "concurrently": "7.6.0", 52 | "cross-env": "7.0.3", 53 | "eslint": "8.19.0", 54 | "eslint-config-prettier": "8.5.0", 55 | "eslint-plugin-import": "2.26.0", 56 | "eslint-plugin-jest": "26.5.3", 57 | "eslint-plugin-jsx-a11y": "6.6.0", 58 | "eslint-plugin-prettier": "4.2.1", 59 | "eslint-plugin-react": "7.30.1", 60 | "eslint-plugin-react-hooks": "4.6.0", 61 | "jest": "28.1.3", 62 | "jest-environment-jsdom": "28.1.3", 63 | "npm-run-all": "4.1.5", 64 | "png.js": "0.2.1", 65 | "prettier": "2.7.1", 66 | "prettier-plugin-jsdoc": "0.4.2", 67 | "react": "18.1.0", 68 | "react-dom": "18.1.0", 69 | "rimraf": "3.0.2", 70 | "rollup": "2.79.1", 71 | "rollup-plugin-babel": "4.4.0", 72 | "rollup-plugin-sourcemaps": "0.6.3", 73 | "rollup-plugin-terser": "7.0.2", 74 | "turbo": "1.8.3", 75 | "typescript": "4.7.4", 76 | "verdaccio": "5.21.2" 77 | }, 78 | "changelog": { 79 | "repo": "kmkzt/svg-drawing", 80 | "labels": { 81 | "breaking": "Breaking Change", 82 | "enhancement": "Enhancement", 83 | "feature": "Feature", 84 | "dependencies": "Dependencies", 85 | "bug": "Bug Fix", 86 | "documentation": "Documentation", 87 | "internal": "Internal" 88 | }, 89 | "cacheDir": ".changelog" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_core/Path.md: -------------------------------------------------------------------------------- 1 | # Class: Path 2 | 3 | [@svg-drawing/core](../../modules/svg_drawing_core.md).Path 4 | 5 | Cannot support commands that use `M` or` z` more than once `M 0 0 L 1 1 Z M 1 6 | 1 L 2 2 Z` 7 | 8 | ## Constructors 9 | 10 | ### constructor 11 | 12 | • **new Path**(`__namedParameters?`) 13 | 14 | #### Parameters 15 | 16 | | Name | Type | 17 | | :------ | :------ | 18 | | `__namedParameters` | [`PathObject`](../../modules/svg_drawing_core.md#pathobject) | 19 | 20 | #### Defined in 21 | 22 | [core/src/svg.ts:153](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L153) 23 | 24 | ## Properties 25 | 26 | ### attrs 27 | 28 | • **attrs**: [`PathObject`](../../modules/svg_drawing_core.md#pathobject) 29 | 30 | #### Defined in 31 | 32 | [core/src/svg.ts:150](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L150) 33 | 34 | ___ 35 | 36 | ### commands 37 | 38 | • **commands**: [`Command`](Command.md)[] 39 | 40 | #### Defined in 41 | 42 | [core/src/svg.ts:151](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L151) 43 | 44 | ## Methods 45 | 46 | ### addCommand 47 | 48 | ▸ **addCommand**(`param`): [`Path`](Path.md) 49 | 50 | #### Parameters 51 | 52 | | Name | Type | 53 | | :------ | :------ | 54 | | `param` | [`Command`](Command.md) \| [`Command`](Command.md)[] | 55 | 56 | #### Returns 57 | 58 | [`Path`](Path.md) 59 | 60 | #### Defined in 61 | 62 | [core/src/svg.ts:165](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L165) 63 | 64 | ___ 65 | 66 | ### clone 67 | 68 | ▸ **clone**(): [`Path`](Path.md) 69 | 70 | #### Returns 71 | 72 | [`Path`](Path.md) 73 | 74 | #### Defined in 75 | 76 | [core/src/svg.ts:236](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L236) 77 | 78 | ___ 79 | 80 | ### getCommandString 81 | 82 | ▸ **getCommandString**(): `string` 83 | 84 | #### Returns 85 | 86 | `string` 87 | 88 | #### Defined in 89 | 90 | [core/src/svg.ts:173](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L173) 91 | 92 | ___ 93 | 94 | ### parseCommandString 95 | 96 | ▸ **parseCommandString**(`d`): `void` 97 | 98 | #### Parameters 99 | 100 | | Name | Type | 101 | | :------ | :------ | 102 | | `d` | `string` | 103 | 104 | #### Returns 105 | 106 | `void` 107 | 108 | #### Defined in 109 | 110 | [core/src/svg.ts:182](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L182) 111 | 112 | ___ 113 | 114 | ### parsePathElement 115 | 116 | ▸ **parsePathElement**(`pEl`): [`Path`](Path.md) 117 | 118 | #### Parameters 119 | 120 | | Name | Type | 121 | | :------ | :------ | 122 | | `pEl` | `SVGPathElement` | 123 | 124 | #### Returns 125 | 126 | [`Path`](Path.md) 127 | 128 | #### Defined in 129 | 130 | [core/src/svg.ts:213](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L213) 131 | 132 | ___ 133 | 134 | ### scale 135 | 136 | ▸ **scale**(`r`): [`Path`](Path.md) 137 | 138 | #### Parameters 139 | 140 | | Name | Type | 141 | | :------ | :------ | 142 | | `r` | `number` | 143 | 144 | #### Returns 145 | 146 | [`Path`](Path.md) 147 | 148 | #### Defined in 149 | 150 | [core/src/svg.ts:159](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L159) 151 | 152 | ___ 153 | 154 | ### toJson 155 | 156 | ▸ **toJson**(): [`PathObject`](../../modules/svg_drawing_core.md#pathobject) 157 | 158 | #### Returns 159 | 160 | [`PathObject`](../../modules/svg_drawing_core.md#pathobject) 161 | 162 | #### Defined in 163 | 164 | [core/src/svg.ts:229](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L229) 165 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_core/Command.md: -------------------------------------------------------------------------------- 1 | # Class: Command 2 | 3 | [@svg-drawing/core](../../modules/svg_drawing_core.md).Command 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new Command**(`type`, `value?`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | Default value | 14 | | :------ | :------ | :------ | 15 | | `type` | [`CommandType`](../../modules/svg_drawing_core.md#commandtype) | `undefined` | 16 | | `value` | `number`[] | `[]` | 17 | 18 | #### Defined in 19 | 20 | [core/src/svg.ts:62](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L62) 21 | 22 | ## Properties 23 | 24 | ### type 25 | 26 | • **type**: [`CommandType`](../../modules/svg_drawing_core.md#commandtype) 27 | 28 | #### Defined in 29 | 30 | [core/src/svg.ts:59](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L59) 31 | 32 | ___ 33 | 34 | ### value 35 | 36 | • **value**: `number`[] 37 | 38 | #### Defined in 39 | 40 | [core/src/svg.ts:60](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L60) 41 | 42 | ## Accessors 43 | 44 | ### cl 45 | 46 | • `get` **cl**(): `undefined` \| [`Point`](Point.md) 47 | 48 | #### Returns 49 | 50 | `undefined` \| [`Point`](Point.md) 51 | 52 | #### Defined in 53 | 54 | [core/src/svg.ts:90](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L90) 55 | 56 | • `set` **cl**(`po`): `void` 57 | 58 | #### Parameters 59 | 60 | | Name | Type | 61 | | :------ | :------ | 62 | | `po` | `undefined` \| [`Point`](Point.md) | 63 | 64 | #### Returns 65 | 66 | `void` 67 | 68 | #### Defined in 69 | 70 | [core/src/svg.ts:82](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L82) 71 | 72 | ___ 73 | 74 | ### cr 75 | 76 | • `get` **cr**(): `undefined` \| [`Point`](Point.md) 77 | 78 | #### Returns 79 | 80 | `undefined` \| [`Point`](Point.md) 81 | 82 | #### Defined in 83 | 84 | [core/src/svg.ts:75](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L75) 85 | 86 | • `set` **cr**(`po`): `void` 87 | 88 | #### Parameters 89 | 90 | | Name | Type | 91 | | :------ | :------ | 92 | | `po` | `undefined` \| [`Point`](Point.md) | 93 | 94 | #### Returns 95 | 96 | `void` 97 | 98 | #### Defined in 99 | 100 | [core/src/svg.ts:67](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L67) 101 | 102 | ___ 103 | 104 | ### point 105 | 106 | • `get` **point**(): `undefined` \| [`Point`](Point.md) 107 | 108 | #### Returns 109 | 110 | `undefined` \| [`Point`](Point.md) 111 | 112 | #### Defined in 113 | 114 | [core/src/svg.ts:103](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L103) 115 | 116 | • `set` **point**(`po`): `void` 117 | 118 | #### Parameters 119 | 120 | | Name | Type | 121 | | :------ | :------ | 122 | | `po` | `undefined` \| [`Point`](Point.md) | 123 | 124 | #### Returns 125 | 126 | `void` 127 | 128 | #### Defined in 129 | 130 | [core/src/svg.ts:98](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L98) 131 | 132 | ## Methods 133 | 134 | ### clone 135 | 136 | ▸ **clone**(): [`Command`](Command.md) 137 | 138 | #### Returns 139 | 140 | [`Command`](Command.md) 141 | 142 | #### Defined in 143 | 144 | [core/src/svg.ts:121](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L121) 145 | 146 | ___ 147 | 148 | ### scale 149 | 150 | ▸ **scale**(`r`): [`Command`](Command.md) 151 | 152 | #### Parameters 153 | 154 | | Name | Type | 155 | | :------ | :------ | 156 | | `r` | `number` | 157 | 158 | #### Returns 159 | 160 | [`Command`](Command.md) 161 | 162 | #### Defined in 163 | 164 | [core/src/svg.ts:113](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L113) 165 | 166 | ___ 167 | 168 | ### toString 169 | 170 | ▸ **toString**(): `string` 171 | 172 | #### Returns 173 | 174 | `string` 175 | 176 | #### Defined in 177 | 178 | [core/src/svg.ts:108](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L108) 179 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | install: 13 | name: install 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node-version: [18, 16, 14] 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | 22 | - name: Use Node ${{ matrix.node-version }} 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | 27 | - name: Use cached node_modules 28 | id: use_cached_node_modules 29 | uses: actions/cache@v3 30 | with: 31 | path: node_modules 32 | key: nodeModules-${{ matrix.node-version }}-${{ hashFiles('yarn.lock') }} 33 | 34 | - name: install 35 | if: steps.use_cached_node_modules.outputs.cache-hit != 'true' 36 | run: yarn install --frozen-lockfile --prefer-offline 37 | 38 | build: 39 | name: build 40 | needs: install 41 | runs-on: ubuntu-latest 42 | strategy: 43 | matrix: 44 | node-version: [18, 16, 14] 45 | 46 | steps: 47 | - name: Checkout 48 | uses: actions/checkout@v3 49 | 50 | - name: Use Node ${{ matrix.node-version }} 51 | uses: actions/setup-node@v3 52 | with: 53 | node-version: ${{ matrix.node-version }} 54 | 55 | - name: Restore cached node_modules 56 | uses: actions/cache/restore@v3 57 | with: 58 | path: node_modules 59 | key: nodeModules-${{ matrix.node-version }}-${{ hashFiles('yarn.lock') }} 60 | 61 | - name: build 62 | run: | 63 | yarn build 64 | yarn build:gh-page 65 | 66 | lint: 67 | name: lint 68 | needs: install 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: Checkout 72 | uses: actions/checkout@v3 73 | 74 | - name: Use Node 18 75 | uses: actions/setup-node@v3 76 | with: 77 | node-version: 18 78 | 79 | - name: Restore cached node_modules 80 | uses: actions/cache/restore@v3 81 | with: 82 | path: node_modules 83 | key: nodeModules-18-${{ hashFiles('yarn.lock') }} 84 | 85 | - name: lint 86 | run: yarn lint 87 | 88 | type-check: 89 | name: Type check 90 | needs: install 91 | runs-on: ubuntu-latest 92 | steps: 93 | - name: Checkout 94 | uses: actions/checkout@v3 95 | 96 | - name: Use Node 18 97 | uses: actions/setup-node@v3 98 | with: 99 | node-version: 18 100 | 101 | - name: Restore cached node_modules 102 | uses: actions/cache/restore@v3 103 | with: 104 | path: node_modules 105 | key: nodeModules-18-${{ hashFiles('yarn.lock') }} 106 | 107 | - name: typecheck 108 | run: yarn typecheck 109 | 110 | generate-docs: 111 | name: Generate document 112 | needs: install 113 | runs-on: ubuntu-latest 114 | steps: 115 | - name: Checkout 116 | uses: actions/checkout@v3 117 | 118 | - name: Use Node 18 119 | uses: actions/setup-node@v3 120 | with: 121 | node-version: 18 122 | 123 | - name: Restore cached node_modules 124 | uses: actions/cache/restore@v3 125 | with: 126 | path: node_modules 127 | key: nodeModules-18-${{ hashFiles('yarn.lock') }} 128 | 129 | - name: generate docs 130 | run: yarn generate:docs 131 | 132 | test: 133 | name: Test 134 | needs: install 135 | runs-on: ubuntu-latest 136 | 137 | steps: 138 | - name: Checkout 139 | uses: actions/checkout@v3 140 | 141 | - name: Use Node 18 142 | uses: actions/setup-node@v3 143 | with: 144 | node-version: 18 145 | 146 | - name: Restore cached node_modules 147 | uses: actions/cache/restore@v3 148 | with: 149 | path: node_modules 150 | key: nodeModules-18-${{ hashFiles('yarn.lock') }} 151 | 152 | - name: build for test 153 | run: yarn build 154 | 155 | - name: test 156 | run: yarn test 157 | 158 | - name: Upload coverage to Codecov 159 | uses: codecov/codecov-action@v3.1.0 160 | with: 161 | token: ${{secrets.CODECOV_TOKEN}} 162 | file: ./coverage/clover.xml 163 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_animation/SvgAnimation.md: -------------------------------------------------------------------------------- 1 | # Class: SvgAnimation 2 | 3 | [@svg-drawing/animation](../../modules/svg_drawing_animation.md).SvgAnimation 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new SvgAnimation**(`el`, `__namedParameters?`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `el` | `HTMLElement` | 16 | | `__namedParameters` | [`AnimationOption`](../../modules/svg_drawing_animation.md#animationoption) | 17 | 18 | #### Defined in 19 | 20 | [animation/src/animation.ts:31](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L31) 21 | 22 | ## Properties 23 | 24 | ### ms 25 | 26 | • **ms**: `number` 27 | 28 | Options 29 | 30 | #### Defined in 31 | 32 | [animation/src/animation.ts:18](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L18) 33 | 34 | ___ 35 | 36 | ### renderer 37 | 38 | • **renderer**: `Renderer` 39 | 40 | #### Defined in 41 | 42 | [animation/src/animation.ts:27](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L27) 43 | 44 | ___ 45 | 46 | ### resizeHandler 47 | 48 | • **resizeHandler**: `ResizeHandler` 49 | 50 | #### Defined in 51 | 52 | [animation/src/animation.ts:28](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L28) 53 | 54 | ___ 55 | 56 | ### svg 57 | 58 | • **svg**: `Svg` 59 | 60 | Modules 61 | 62 | #### Defined in 63 | 64 | [animation/src/animation.ts:26](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L26) 65 | 66 | ## Methods 67 | 68 | ### download 69 | 70 | ▸ **download**(`filename?`): `void` 71 | 72 | #### Parameters 73 | 74 | | Name | Type | 75 | | :------ | :------ | 76 | | `filename?` | `string` | 77 | 78 | #### Returns 79 | 80 | `void` 81 | 82 | #### Defined in 83 | 84 | [animation/src/animation.ts:233](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L233) 85 | 86 | ___ 87 | 88 | ### generateFrame 89 | 90 | ▸ **generateFrame**(`index?`): `Path`[] 91 | 92 | #### Parameters 93 | 94 | | Name | Type | 95 | | :------ | :------ | 96 | | `index?` | `number` | 97 | 98 | #### Returns 99 | 100 | `Path`[] 101 | 102 | #### Defined in 103 | 104 | [animation/src/animation.ts:88](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L88) 105 | 106 | ___ 107 | 108 | ### restore 109 | 110 | ▸ **restore**(): `void` 111 | 112 | #### Returns 113 | 114 | `void` 115 | 116 | #### Defined in 117 | 118 | [animation/src/animation.ts:83](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L83) 119 | 120 | ___ 121 | 122 | ### setAnimation 123 | 124 | ▸ **setAnimation**(`fn`, `opts?`): `void` 125 | 126 | #### Parameters 127 | 128 | | Name | Type | Description | 129 | | :------ | :------ | :------ | 130 | | `fn` | [`FrameAnimation`](../../modules/svg_drawing_animation.md#frameanimation) | | 131 | | `opts` | `Object` | `frame` is the number of frames to animate `repeat` is related for repeatCount of animate element attribute. | 132 | | `opts.frames?` | `number` | - | 133 | | `opts.ms?` | `number` | - | 134 | | `opts.repeatCount?` | `string` \| `number` | - | 135 | 136 | #### Returns 137 | 138 | `void` 139 | 140 | #### Defined in 141 | 142 | [animation/src/animation.ts:60](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L60) 143 | 144 | ___ 145 | 146 | ### start 147 | 148 | ▸ **start**(): `void` 149 | 150 | #### Returns 151 | 152 | `void` 153 | 154 | #### Defined in 155 | 156 | [animation/src/animation.ts:96](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L96) 157 | 158 | ___ 159 | 160 | ### stop 161 | 162 | ▸ **stop**(): `boolean` 163 | 164 | #### Returns 165 | 166 | `boolean` 167 | 168 | #### Defined in 169 | 170 | [animation/src/animation.ts:74](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L74) 171 | 172 | ___ 173 | 174 | ### toElement 175 | 176 | ▸ **toElement**(): `SVGSVGElement` 177 | 178 | #### Returns 179 | 180 | `SVGSVGElement` 181 | 182 | #### Defined in 183 | 184 | [animation/src/animation.ts:133](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L133) 185 | 186 | ___ 187 | 188 | ### update 189 | 190 | ▸ **update**(): `void` 191 | 192 | #### Returns 193 | 194 | `void` 195 | 196 | #### Defined in 197 | 198 | [animation/src/animation.ts:129](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/animation/src/animation.ts#L129) 199 | -------------------------------------------------------------------------------- /packages/img-trace/src/blur.ts: -------------------------------------------------------------------------------- 1 | import { convertRGBAImage } from './utils/convertRGBAImage' 2 | 3 | // Gaussian kernels for blur 4 | const gks: number[][] = [ 5 | [0.27901, 0.44198, 0.27901], 6 | [0.135336, 0.228569, 0.272192, 0.228569, 0.135336], 7 | [0.086776, 0.136394, 0.178908, 0.195843, 0.178908, 0.136394, 0.086776], 8 | [ 9 | 0.063327, 0.093095, 0.122589, 0.144599, 0.152781, 0.144599, 0.122589, 10 | 0.093095, 0.063327, 11 | ], 12 | [ 13 | 0.049692, 0.069304, 0.089767, 0.107988, 0.120651, 0.125194, 0.120651, 14 | 0.107988, 0.089767, 0.069304, 0.049692, 15 | ], 16 | ] 17 | 18 | export interface BlurOption { 19 | radius?: number 20 | delta?: number 21 | } 22 | export class Blur { 23 | public radius: number 24 | public delta: number 25 | constructor({ radius, delta }: BlurOption) { 26 | this.radius = radius ?? 0 27 | this.delta = delta ?? 20 28 | } 29 | 30 | public apply(argimgd: ImageData): ImageData { 31 | const imgd = convertRGBAImage(argimgd) 32 | const data: Uint8ClampedArray = new Uint8ClampedArray(imgd.data) 33 | // radius and delta limits, this kernel 34 | let radius = Math.floor(this.radius) 35 | if (radius < 1) { 36 | return imgd 37 | } 38 | if (radius > gks.length) { 39 | radius = gks.length 40 | } 41 | let delta = Math.abs(this.delta) 42 | if (delta > 1024) { 43 | delta = 1024 44 | } 45 | const thisgk = gks[radius - 1] 46 | 47 | // loop through all pixels, horizontal blur 48 | for (let j = 0; j < imgd.height; j++) { 49 | for (let i = 0; i < imgd.width; i++) { 50 | let racc = 0 51 | let gacc = 0 52 | let bacc = 0 53 | let aacc = 0 54 | let wacc = 0 55 | // gauss kernel loop 56 | for (let k = -radius; k < radius + 1; k++) { 57 | // add weighted color values 58 | if (i + k > 0 && i + k < imgd.width) { 59 | const idx = (j * imgd.width + i + k) * 4 60 | racc += imgd.data[idx] * thisgk[k + radius] 61 | gacc += imgd.data[idx + 1] * thisgk[k + radius] 62 | bacc += imgd.data[idx + 2] * thisgk[k + radius] 63 | aacc += imgd.data[idx + 3] * thisgk[k + radius] 64 | wacc += thisgk[k + radius] 65 | } 66 | } 67 | // The new pixel 68 | const idx = (j * imgd.width + i) * 4 69 | data[idx] = Math.floor(racc / wacc) 70 | data[idx + 1] = Math.floor(gacc / wacc) 71 | data[idx + 2] = Math.floor(bacc / wacc) 72 | data[idx + 3] = Math.floor(aacc / wacc) 73 | } // End of width loop 74 | } // End of horizontal blur 75 | 76 | // copying the half blurred imgd2 77 | const himgd = new Uint8ClampedArray(data) 78 | 79 | // loop through all pixels, vertical blur 80 | for (let j = 0; j < imgd.height; j++) { 81 | for (let i = 0; i < imgd.width; i++) { 82 | let racc = 0 83 | let gacc = 0 84 | let bacc = 0 85 | let aacc = 0 86 | let wacc = 0 87 | // gauss kernel loop 88 | for (let k = -radius; k < radius + 1; k++) { 89 | // add weighted color values 90 | if (j + k > 0 && j + k < imgd.height) { 91 | const idx = ((j + k) * imgd.width + i) * 4 92 | racc += himgd[idx] * thisgk[k + radius] 93 | gacc += himgd[idx + 1] * thisgk[k + radius] 94 | bacc += himgd[idx + 2] * thisgk[k + radius] 95 | aacc += himgd[idx + 3] * thisgk[k + radius] 96 | wacc += thisgk[k + radius] 97 | } 98 | } 99 | // The new pixel 100 | const idx = (j * imgd.width + i) * 4 101 | data[idx] = Math.floor(racc / wacc) 102 | data[idx + 1] = Math.floor(gacc / wacc) 103 | data[idx + 2] = Math.floor(bacc / wacc) 104 | data[idx + 3] = Math.floor(aacc / wacc) 105 | } // End of width loop 106 | } // End of vertical blur 107 | // Selective blur: loop through all pixels 108 | for (let j = 0; j < imgd.height; j++) { 109 | for (let i = 0; i < imgd.width; i++) { 110 | const idx = (j * imgd.width + i) * 4 111 | // d is the difference between the blurred and the original pixel 112 | const d = 113 | Math.abs(data[idx] - imgd.data[idx]) + 114 | Math.abs(data[idx + 1] - imgd.data[idx + 1]) + 115 | Math.abs(data[idx + 2] - imgd.data[idx + 2]) + 116 | Math.abs(data[idx + 3] - imgd.data[idx + 3]) 117 | // selective blur: if d>delta, put the original pixel back 118 | if (d > delta) { 119 | data[idx] = imgd.data[idx] 120 | data[idx + 1] = imgd.data[idx + 1] 121 | data[idx + 2] = imgd.data[idx + 2] 122 | data[idx + 3] = imgd.data[idx + 3] 123 | } 124 | } 125 | } // End of Selective blur 126 | return new ImageData(data, imgd.width, imgd.height) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_core/Svg.md: -------------------------------------------------------------------------------- 1 | # Class: Svg 2 | 3 | [@svg-drawing/core](../../modules/svg_drawing_core.md).Svg 4 | 5 | ## Constructors 6 | 7 | ### constructor 8 | 9 | • **new Svg**(`__namedParameters`) 10 | 11 | #### Parameters 12 | 13 | | Name | Type | 14 | | :------ | :------ | 15 | | `__namedParameters` | [`SvgOption`](../../modules/svg_drawing_core.md#svgoption) | 16 | 17 | #### Defined in 18 | 19 | [core/src/svg.ts:250](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L250) 20 | 21 | ## Properties 22 | 23 | ### background 24 | 25 | • `Optional` **background**: `string` 26 | 27 | #### Defined in 28 | 29 | [core/src/svg.ts:248](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L248) 30 | 31 | ___ 32 | 33 | ### height 34 | 35 | • **height**: `number` 36 | 37 | #### Defined in 38 | 39 | [core/src/svg.ts:247](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L247) 40 | 41 | ___ 42 | 43 | ### paths 44 | 45 | • **paths**: [`Path`](Path.md)[] 46 | 47 | #### Defined in 48 | 49 | [core/src/svg.ts:245](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L245) 50 | 51 | ___ 52 | 53 | ### width 54 | 55 | • **width**: `number` 56 | 57 | #### Defined in 58 | 59 | [core/src/svg.ts:246](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L246) 60 | 61 | ## Methods 62 | 63 | ### addPath 64 | 65 | ▸ **addPath**(`pa`): [`Svg`](Svg.md) 66 | 67 | #### Parameters 68 | 69 | | Name | Type | 70 | | :------ | :------ | 71 | | `pa` | [`Path`](Path.md) \| [`Path`](Path.md)[] | 72 | 73 | #### Returns 74 | 75 | [`Svg`](Svg.md) 76 | 77 | #### Defined in 78 | 79 | [core/src/svg.ts:273](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L273) 80 | 81 | ___ 82 | 83 | ### clonePaths 84 | 85 | ▸ **clonePaths**(): [`Path`](Path.md)[] 86 | 87 | #### Returns 88 | 89 | [`Path`](Path.md)[] 90 | 91 | #### Defined in 92 | 93 | [core/src/svg.ts:282](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L282) 94 | 95 | ___ 96 | 97 | ### copy 98 | 99 | ▸ **copy**(`svg`): [`Svg`](Svg.md) 100 | 101 | #### Parameters 102 | 103 | | Name | Type | 104 | | :------ | :------ | 105 | | `svg` | [`Svg`](Svg.md) | 106 | 107 | #### Returns 108 | 109 | [`Svg`](Svg.md) 110 | 111 | #### Defined in 112 | 113 | [core/src/svg.ts:302](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L302) 114 | 115 | ___ 116 | 117 | ### parseSVGElement 118 | 119 | ▸ **parseSVGElement**(`svgEl`): [`Svg`](Svg.md) 120 | 121 | #### Parameters 122 | 123 | | Name | Type | 124 | | :------ | :------ | 125 | | `svgEl` | `SVGSVGElement` | 126 | 127 | #### Returns 128 | 129 | [`Svg`](Svg.md) 130 | 131 | #### Defined in 132 | 133 | [core/src/svg.ts:321](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L321) 134 | 135 | ___ 136 | 137 | ### parseSVGString 138 | 139 | ▸ **parseSVGString**(`svgStr`): [`Svg`](Svg.md) 140 | 141 | #### Parameters 142 | 143 | | Name | Type | 144 | | :------ | :------ | 145 | | `svgStr` | `string` | 146 | 147 | #### Returns 148 | 149 | [`Svg`](Svg.md) 150 | 151 | #### Defined in 152 | 153 | [core/src/svg.ts:310](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L310) 154 | 155 | ___ 156 | 157 | ### resize 158 | 159 | ▸ **resize**(`__namedParameters`): `void` 160 | 161 | #### Parameters 162 | 163 | | Name | Type | 164 | | :------ | :------ | 165 | | `__namedParameters` | `Object` | 166 | | `__namedParameters.height` | `number` | 167 | | `__namedParameters.width` | `number` | 168 | 169 | #### Returns 170 | 171 | `void` 172 | 173 | #### Defined in 174 | 175 | [core/src/svg.ts:258](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L258) 176 | 177 | ___ 178 | 179 | ### scalePath 180 | 181 | ▸ **scalePath**(`r`): [`Svg`](Svg.md) 182 | 183 | #### Parameters 184 | 185 | | Name | Type | 186 | | :------ | :------ | 187 | | `r` | `number` | 188 | 189 | #### Returns 190 | 191 | [`Svg`](Svg.md) 192 | 193 | #### Defined in 194 | 195 | [core/src/svg.ts:264](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L264) 196 | 197 | ___ 198 | 199 | ### toJson 200 | 201 | ▸ **toJson**(): [`SvgObject`](../../modules/svg_drawing_core.md#svgobject) 202 | 203 | #### Returns 204 | 205 | [`SvgObject`](../../modules/svg_drawing_core.md#svgobject) 206 | 207 | #### Defined in 208 | 209 | [core/src/svg.ts:293](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L293) 210 | 211 | ___ 212 | 213 | ### updatePath 214 | 215 | ▸ **updatePath**(`pa`, `i?`): [`Svg`](Svg.md) 216 | 217 | #### Parameters 218 | 219 | | Name | Type | 220 | | :------ | :------ | 221 | | `pa` | [`Path`](Path.md) | 222 | | `i?` | `number` | 223 | 224 | #### Returns 225 | 226 | [`Svg`](Svg.md) 227 | 228 | #### Defined in 229 | 230 | [core/src/svg.ts:286](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/svg.ts#L286) 231 | -------------------------------------------------------------------------------- /packages/core/src/__snapshots__/svg.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`svg.ts Path commands parameter and getCommandString Normal 1`] = ` 4 | Array [ 5 | Command { 6 | "type": "M", 7 | "value": Array [ 8 | 0, 9 | 0, 10 | ], 11 | }, 12 | Command { 13 | "type": "L", 14 | "value": Array [ 15 | 1, 16 | 1, 17 | ], 18 | }, 19 | Command { 20 | "type": "L", 21 | "value": Array [ 22 | -1, 23 | -1, 24 | ], 25 | }, 26 | ] 27 | `; 28 | 29 | exports[`svg.ts Path commands parameter and getCommandString Normal 2`] = `"M 0 0 L 1 1 L -1 -1"`; 30 | 31 | exports[`svg.ts Path toJson and toElement toElement 1`] = ` 32 | 35 | `; 36 | 37 | exports[`svg.ts Path toJson and toElement toJson 1`] = ` 38 | Object { 39 | "d": "M 0 0 L 1 1 L 2 1 L 3 0", 40 | } 41 | `; 42 | 43 | exports[`svg.ts Svg download svg download 1`] = ` 44 | Object { 45 | "data": "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIGhlaWdodD0iNCIgd2lkdGg9IjQiPjxwYXRoIGQ9Ik0gMCAwIEMgMC4yIDAuMiAwLjYgMC44IDEgMSBDIDEuNCAxLjIgMS42IDEuMiAyIDEgQyAyLjQgMC44IDIuOCAwLjIgMyAwIj48L3BhdGg+PHBhdGggZD0iTSA0IDQgTCA5IDQgTCA5IDggTCAzIDAgWiIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgc3Ryb2tlLWxpbmVqb2luPSJtaXR0ZXIiPjwvcGF0aD48L3N2Zz4=", 46 | "extension": "svg", 47 | "filename": undefined, 48 | } 49 | `; 50 | 51 | exports[`svg.ts Svg parseSVGElement 1`] = ` 52 | Svg { 53 | "background": undefined, 54 | "height": 400, 55 | "paths": Array [ 56 | Path { 57 | "attrs": Object { 58 | "fill": "#f00", 59 | "stroke": "#00f", 60 | "strokeWidth": "8", 61 | }, 62 | "commands": Array [ 63 | Command { 64 | "type": "M", 65 | "value": Array [ 66 | 2, 67 | 2, 68 | ], 69 | }, 70 | Command { 71 | "type": "L", 72 | "value": Array [ 73 | 4, 74 | 4, 75 | ], 76 | }, 77 | Command { 78 | "type": "C", 79 | "value": Array [ 80 | 6, 81 | 6, 82 | 10, 83 | 6, 84 | 14, 85 | 6, 86 | ], 87 | }, 88 | Command { 89 | "type": "Z", 90 | "value": Array [], 91 | }, 92 | ], 93 | }, 94 | ], 95 | "width": 400, 96 | } 97 | `; 98 | 99 | exports[`svg.ts Svg parseSVGString 1`] = ` 100 | Svg { 101 | "background": undefined, 102 | "height": 400, 103 | "paths": Array [ 104 | Path { 105 | "attrs": Object { 106 | "fill": "#f00", 107 | "stroke": "#00f", 108 | "strokeWidth": "8", 109 | }, 110 | "commands": Array [ 111 | Command { 112 | "type": "M", 113 | "value": Array [ 114 | 2, 115 | 2, 116 | ], 117 | }, 118 | Command { 119 | "type": "L", 120 | "value": Array [ 121 | 4, 122 | 4, 123 | ], 124 | }, 125 | Command { 126 | "type": "C", 127 | "value": Array [ 128 | 6, 129 | 6, 130 | 10, 131 | 6, 132 | 14, 133 | 6, 134 | ], 135 | }, 136 | Command { 137 | "type": "Z", 138 | "value": Array [], 139 | }, 140 | ], 141 | }, 142 | ], 143 | "width": 400, 144 | } 145 | `; 146 | 147 | exports[`svg.ts Svg toBase64 1`] = `"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIGhlaWdodD0iNTAwIiB3aWR0aD0iNTAwIj48cGF0aCBkPSJNIDAgMCBDIDAuMiAwLjIgMC42IDAuOCAxIDEgQyAxLjQgMS4yIDEuNiAxLjIgMiAxIEMgMi40IDAuOCAyLjggMC4yIDMgMCI+PC9wYXRoPjxwYXRoIGQ9Ik0gNCA0IEwgOSA0IEwgOSA4IEwgMyAwIFoiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIHN0cm9rZS1saW5lam9pbj0ibWl0dGVyIj48L3BhdGg+PC9zdmc+"`; 148 | 149 | exports[`svg.ts Svg toElement 1`] = ` 150 | 157 | 160 | 165 | 166 | `; 167 | 168 | exports[`svg.ts Svg toJson 1`] = ` 169 | Object { 170 | "background": undefined, 171 | "height": 500, 172 | "paths": Array [ 173 | Object { 174 | "d": "M 0 0 C 0.2 0.2 0.6 0.8 1 1 C 1.4 1.2 1.6 1.2 2 1 C 2.4 0.8 2.8 0.2 3 0", 175 | }, 176 | Object { 177 | "d": "M 4 4 L 9 4 L 9 8 L 3 0 Z", 178 | "strokeLinecap": "square", 179 | "strokeLinejoin": "mitter", 180 | }, 181 | ], 182 | "width": 500, 183 | } 184 | `; 185 | -------------------------------------------------------------------------------- /packages/img-trace/src/palette.ts: -------------------------------------------------------------------------------- 1 | import { convertRGBAImage } from './utils/convertRGBAImage' 2 | 3 | export interface Rgba { 4 | r: number 5 | g: number 6 | b: number 7 | a: number 8 | } 9 | 10 | const DEFAULT_NUMBER_OF_COLORS = 16 11 | export interface FromImageDataOptions { 12 | numberOfColors?: number 13 | colorQuantCycles?: number 14 | } 15 | export class Palette { 16 | // Deterministic sampling a palette from imagedata: rectangular grid 17 | public static imageData( 18 | argimgd: ImageData, 19 | { numberOfColors, colorQuantCycles }: FromImageDataOptions = {} 20 | ): Rgba[] { 21 | const nc = numberOfColors || DEFAULT_NUMBER_OF_COLORS 22 | const cqc = colorQuantCycles || 3 23 | const imgd = convertRGBAImage(argimgd) 24 | 25 | const palette = this._deterministic(imgd, nc) 26 | let paletteacc: { 27 | r: number 28 | g: number 29 | b: number 30 | a: number 31 | n: number 32 | }[] = [] 33 | // Using a form of k-means clustering repeatead options.colorquantcycles times. http://en.wikipedia.org/wiki/Color_quantization 34 | for (let cnt = 0; cnt < cqc; cnt++) { 35 | if (cnt > 0) { 36 | for (let k = 0; k < palette.length; k++) { 37 | // averaging 38 | if (paletteacc[k].n > 0) { 39 | palette[k] = { 40 | r: Math.floor(paletteacc[k].r / paletteacc[k].n), 41 | g: Math.floor(paletteacc[k].g / paletteacc[k].n), 42 | b: Math.floor(paletteacc[k].b / paletteacc[k].n), 43 | a: Math.floor(paletteacc[k].a / paletteacc[k].n), 44 | } 45 | } 46 | } 47 | } 48 | 49 | paletteacc = Array.from({ length: palette.length }, () => ({ 50 | r: 0, 51 | g: 0, 52 | b: 0, 53 | a: 0, 54 | n: 0, 55 | })) 56 | 57 | // loop through all pixels 58 | for (let j = 0; j < imgd.height; j++) { 59 | for (let i = 0; i < imgd.width; i++) { 60 | // pixel index 61 | const idx = (j * imgd.width + i) * 4 62 | 63 | // find closest color from palette by measuring (rectilinear) color distance between this pixel and all palette colors 64 | // In my experience, https://en.wikipedia.org/wiki/Rectilinear_distance works better than https://en.wikipedia.org/wiki/Euclidean_distance 65 | let cdl = 1024 // 4 * 256 is the maximum RGBA distance 66 | const ci = palette.reduce((findId: number, pal: Rgba, id: number) => { 67 | const cd = 68 | Math.abs(pal.r - imgd.data[idx]) + 69 | Math.abs(pal.g - imgd.data[idx + 1]) + 70 | Math.abs(pal.b - imgd.data[idx + 2]) + 71 | Math.abs(pal.a - imgd.data[idx + 3]) 72 | if (cd < cdl) { 73 | cdl = cd 74 | return id 75 | } 76 | return findId 77 | }, 0) 78 | 79 | // add to palettacc 80 | paletteacc[ci].r += imgd.data[idx] 81 | paletteacc[ci].g += imgd.data[idx + 1] 82 | paletteacc[ci].b += imgd.data[idx + 2] 83 | paletteacc[ci].a += imgd.data[idx + 3] 84 | paletteacc[ci].n += 1 85 | } 86 | } 87 | } 88 | return palette 89 | } 90 | 91 | // Deterministic sampling a palette from imagedata: rectangular grid 92 | private static _deterministic( 93 | imgd: ImageData, 94 | numberOfColors: number 95 | ): Rgba[] { 96 | const palette: Rgba[] = [] 97 | const ni = Math.ceil(Math.sqrt(numberOfColors)) 98 | const nj = Math.ceil(numberOfColors / ni) 99 | const vx = imgd.width / (ni + 1) 100 | const vy = imgd.height / (nj + 1) 101 | for (let j = 0; j < nj; j++) { 102 | for (let i = 0; i < ni; i++) { 103 | if (palette.length === numberOfColors) break 104 | const idx = Math.floor((j + 1) * vy * imgd.width + (i + 1) * vx) * 4 105 | palette.push({ 106 | r: imgd.data[idx], 107 | g: imgd.data[idx + 1], 108 | b: imgd.data[idx + 2], 109 | a: imgd.data[idx + 3], 110 | }) 111 | } 112 | } 113 | return palette 114 | } 115 | // Generating a palette with number of colors 116 | public static number( 117 | numberofcolors: number = DEFAULT_NUMBER_OF_COLORS 118 | ): Rgba[] { 119 | if (numberofcolors < 8) return this.grey(numberofcolors) 120 | const palette: Rgba[] = [] 121 | const colorqnum = Math.floor(Math.pow(numberofcolors, 1 / 3)) 122 | const colorstep = Math.floor(255 / (colorqnum - 1)) 123 | 124 | for (let r = 0; r < colorqnum; r += 1) { 125 | for (let g = 0; g < colorqnum; g += 1) { 126 | for (let b = 0; b < colorqnum; b += 1) { 127 | palette.push({ 128 | r: r * colorstep, 129 | g: g * colorstep, 130 | b: b * colorstep, 131 | a: 255, 132 | }) 133 | } 134 | } 135 | } 136 | 137 | // TODO: rest improve 138 | // TODO: test 139 | const rest = numberofcolors - colorqnum * colorqnum * colorqnum 140 | for (let rcnt = 0; rcnt < rest; rcnt++) { 141 | palette.push({ 142 | r: Math.floor(Math.random() * 255), 143 | g: Math.floor(Math.random() * 255), 144 | b: Math.floor(Math.random() * 255), 145 | a: Math.floor(Math.random() * 255), 146 | }) 147 | } 148 | 149 | return palette 150 | } 151 | 152 | public static grey( 153 | numberofcolors: number = DEFAULT_NUMBER_OF_COLORS 154 | ): Rgba[] { 155 | const palette: Rgba[] = [] 156 | const graystep = Math.floor(255 / (numberofcolors - 1)) 157 | for (let i = 0; i < numberofcolors; i++) { 158 | palette.push({ 159 | r: i * graystep, 160 | g: i * graystep, 161 | b: i * graystep, 162 | a: 255, 163 | }) 164 | } 165 | 166 | return palette 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /packages/core/src/drawing.ts: -------------------------------------------------------------------------------- 1 | import { Convert } from './convert' 2 | import { download } from './download' 3 | import { DrawHandler, ResizeHandler } from './handler' 4 | import { Renderer } from './renderer' 5 | import { Path, Command, COMMAND_TYPE, Svg } from './svg' 6 | import { throttle } from './throttle' 7 | import { isAlmostSameNumber } from './utils' 8 | import type { 9 | DownloadOption, 10 | DrawingOption, 11 | PointObject, 12 | ResizeHandlerCallback, 13 | } from './types' 14 | 15 | /** 16 | * ### Setup SvgDrawing 17 | * 18 | * ```ts 19 | * import { SvgDrawing } from '@svg-drawing/core' 20 | * 21 | * const el = document.getElementById('draw-area') 22 | * new SvgDrawing(el) 23 | * ``` 24 | * 25 | * ### Set draw options. 26 | * 27 | * ```ts 28 | * // It is default value 29 | * const options = { 30 | * penColor: '#000', 31 | * penWidth: 1, 32 | * curve: true, 33 | * close: false, 34 | * delay: 0, 35 | * fill: 'none', 36 | * background: undefined, 37 | * } 38 | * 39 | * new SvgDrawing(el, options) 40 | * ``` 41 | */ 42 | export class SvgDrawing { 43 | // Draw Option 44 | public penColor: string 45 | public penWidth: number 46 | public fill: string 47 | public curve: boolean 48 | public close: boolean 49 | public delay: number 50 | 51 | // Module 52 | public svg: Svg 53 | public convert: Convert 54 | public renderer: Renderer 55 | public drawHandler: DrawHandler 56 | public resizeHandler: ResizeHandler 57 | 58 | // Private property 59 | private _drawPath: Path | null 60 | private _drawPoints: PointObject[] 61 | private _drawMoveThrottle: this['drawMove'] 62 | constructor( 63 | public el: HTMLElement, 64 | { 65 | penColor, 66 | penWidth, 67 | curve, 68 | close, 69 | delay, 70 | fill, 71 | background, 72 | }: DrawingOption = {} 73 | ) { 74 | // Setup Config 75 | this.penColor = penColor ?? '#000' 76 | this.penWidth = penWidth ?? 1 77 | this.curve = curve ?? true 78 | this.close = close ?? false 79 | this.delay = delay ?? 0 80 | this.fill = fill ?? 'none' 81 | 82 | // Setup property 83 | const { width, height } = el.getBoundingClientRect() 84 | this._drawPath = null 85 | this._drawPoints = [] 86 | // Setup Svg 87 | 88 | this.svg = new Svg({ width, height, background }) 89 | // Setup Renderer 90 | 91 | this.renderer = new Renderer(el, { background }) 92 | // Setup BezierCurve 93 | this.convert = new Convert() 94 | 95 | // Setup ResizeHandler 96 | this._resize = this._resize.bind(this) 97 | this.resizeHandler = new ResizeHandler(el, { 98 | resize: this._resize, 99 | }) 100 | // Setup EventDrawHandler 101 | this.drawStart = this.drawStart.bind(this) 102 | this.drawMove = this.drawMove.bind(this) 103 | this._drawMoveThrottle = throttle(this.drawMove, this.delay) 104 | this.drawEnd = this.drawEnd.bind(this) 105 | this.drawHandler = new DrawHandler(el, { 106 | start: this.drawStart, 107 | move: this._drawMoveThrottle, 108 | end: this.drawEnd, 109 | }) 110 | 111 | // Start exec 112 | this.on() 113 | } 114 | 115 | public update() { 116 | this.renderer.update(this.svg.toJson()) 117 | } 118 | 119 | public clear(): Path[] { 120 | const paths = this.svg.paths 121 | this.svg.paths = [] 122 | this.update() 123 | return paths 124 | } 125 | 126 | public undo(): Path | undefined { 127 | const path = this.svg.paths.pop() 128 | this.update() 129 | return path 130 | } 131 | 132 | public changeDelay(delay: number): void { 133 | this.delay = delay 134 | this.drawHandler.move = throttle(this.drawMove, this.delay) 135 | this.drawHandler.on() 136 | } 137 | 138 | public on(): void { 139 | this.drawHandler.on() 140 | this.resizeHandler.on() 141 | } 142 | 143 | public off(): void { 144 | this.drawHandler.off() 145 | this.resizeHandler.off() 146 | } 147 | 148 | public drawStart(): void { 149 | if (this._drawPath) return 150 | this._drawPath = this._createDrawPath() 151 | this.svg.addPath(this._drawPath) 152 | } 153 | 154 | public drawMove(po: PointObject): void { 155 | if (!this._drawPath) return 156 | this._addDrawPoint(po) 157 | if ( 158 | (this._drawPath.attrs.strokeWidth && 159 | +this._drawPath.attrs.strokeWidth !== this.penWidth) || 160 | this._drawPath.attrs.stroke !== this.penColor 161 | ) { 162 | this._drawPath = this._createDrawPath() 163 | this._addDrawPoint(po) 164 | this.svg.addPath(this._drawPath) 165 | } 166 | this.update() 167 | } 168 | 169 | public drawEnd(): void { 170 | this._drawPath = null 171 | this.update() 172 | } 173 | 174 | private _createCommand() { 175 | if (!this._drawPath) return 176 | 177 | if (this.curve) { 178 | this._drawPath.commands = this.convert.bezierCurveCommands( 179 | this._drawPoints 180 | ) 181 | } else { 182 | this._drawPath.commands = this.convert.lineCommands(this._drawPoints) 183 | } 184 | 185 | if (this.close) { 186 | this._drawPath.commands.push(new Command(COMMAND_TYPE.CLOSE)) 187 | } 188 | } 189 | 190 | private _addDrawPoint(p4: PointObject) { 191 | this._drawPoints.push(p4) 192 | this._createCommand() 193 | } 194 | 195 | private _createDrawPath(): Path { 196 | this._resize(this.el.getBoundingClientRect()) 197 | this._drawPoints = [] 198 | return new Path({ 199 | stroke: this.penColor, 200 | strokeWidth: String(this.penWidth), 201 | fill: this.fill, 202 | strokeLinecap: this.curve ? 'round' : 'mitter', 203 | strokeLinejoin: this.curve ? 'round' : 'square', 204 | }) 205 | } 206 | 207 | private _resize({ 208 | width, 209 | height, 210 | }: Parameters[0]) { 211 | if (!isAlmostSameNumber(this.svg.width, width)) { 212 | this.svg.resize({ width, height }) 213 | this.update() 214 | } 215 | } 216 | 217 | public download(opt?: DownloadOption): void { 218 | download(this.svg, opt) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /docs/packages/classes/svg_drawing_core/SvgDrawing.md: -------------------------------------------------------------------------------- 1 | # Class: SvgDrawing 2 | 3 | [@svg-drawing/core](../../modules/svg_drawing_core.md).SvgDrawing 4 | 5 | ### Setup SvgDrawing 6 | 7 | ```ts 8 | import { SvgDrawing } from '@svg-drawing/core' 9 | 10 | const el = document.getElementById('draw-area') 11 | new SvgDrawing(el) 12 | ``` 13 | 14 | ### Set draw options. 15 | 16 | ```ts 17 | // It is default value 18 | const options = { 19 | penColor: '#000', 20 | penWidth: 1, 21 | curve: true, 22 | close: false, 23 | delay: 0, 24 | fill: 'none', 25 | background: undefined, 26 | } 27 | 28 | new SvgDrawing(el, options) 29 | ``` 30 | 31 | ## Constructors 32 | 33 | ### constructor 34 | 35 | • **new SvgDrawing**(`el`, `__namedParameters?`) 36 | 37 | #### Parameters 38 | 39 | | Name | Type | 40 | | :------ | :------ | 41 | | `el` | `HTMLElement` | 42 | | `__namedParameters` | [`DrawingOption`](../../modules/svg_drawing_core.md#drawingoption) | 43 | 44 | #### Defined in 45 | 46 | [core/src/drawing.ts:62](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L62) 47 | 48 | ## Properties 49 | 50 | ### close 51 | 52 | • **close**: `boolean` 53 | 54 | #### Defined in 55 | 56 | [core/src/drawing.ts:48](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L48) 57 | 58 | ___ 59 | 60 | ### convert 61 | 62 | • **convert**: [`Convert`](Convert.md) 63 | 64 | #### Defined in 65 | 66 | [core/src/drawing.ts:53](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L53) 67 | 68 | ___ 69 | 70 | ### curve 71 | 72 | • **curve**: `boolean` 73 | 74 | #### Defined in 75 | 76 | [core/src/drawing.ts:47](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L47) 77 | 78 | ___ 79 | 80 | ### delay 81 | 82 | • **delay**: `number` 83 | 84 | #### Defined in 85 | 86 | [core/src/drawing.ts:49](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L49) 87 | 88 | ___ 89 | 90 | ### drawHandler 91 | 92 | • **drawHandler**: [`DrawHandler`](DrawHandler.md) 93 | 94 | #### Defined in 95 | 96 | [core/src/drawing.ts:55](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L55) 97 | 98 | ___ 99 | 100 | ### el 101 | 102 | • **el**: `HTMLElement` 103 | 104 | ___ 105 | 106 | ### fill 107 | 108 | • **fill**: `string` 109 | 110 | #### Defined in 111 | 112 | [core/src/drawing.ts:46](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L46) 113 | 114 | ___ 115 | 116 | ### penColor 117 | 118 | • **penColor**: `string` 119 | 120 | #### Defined in 121 | 122 | [core/src/drawing.ts:44](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L44) 123 | 124 | ___ 125 | 126 | ### penWidth 127 | 128 | • **penWidth**: `number` 129 | 130 | #### Defined in 131 | 132 | [core/src/drawing.ts:45](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L45) 133 | 134 | ___ 135 | 136 | ### renderer 137 | 138 | • **renderer**: [`Renderer`](Renderer.md) 139 | 140 | #### Defined in 141 | 142 | [core/src/drawing.ts:54](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L54) 143 | 144 | ___ 145 | 146 | ### resizeHandler 147 | 148 | • **resizeHandler**: [`ResizeHandler`](ResizeHandler.md) 149 | 150 | #### Defined in 151 | 152 | [core/src/drawing.ts:56](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L56) 153 | 154 | ___ 155 | 156 | ### svg 157 | 158 | • **svg**: [`Svg`](Svg.md) 159 | 160 | #### Defined in 161 | 162 | [core/src/drawing.ts:52](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L52) 163 | 164 | ## Methods 165 | 166 | ### changeDelay 167 | 168 | ▸ **changeDelay**(`delay`): `void` 169 | 170 | #### Parameters 171 | 172 | | Name | Type | 173 | | :------ | :------ | 174 | | `delay` | `number` | 175 | 176 | #### Returns 177 | 178 | `void` 179 | 180 | #### Defined in 181 | 182 | [core/src/drawing.ts:132](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L132) 183 | 184 | ___ 185 | 186 | ### clear 187 | 188 | ▸ **clear**(): [`Path`](Path.md)[] 189 | 190 | #### Returns 191 | 192 | [`Path`](Path.md)[] 193 | 194 | #### Defined in 195 | 196 | [core/src/drawing.ts:119](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L119) 197 | 198 | ___ 199 | 200 | ### download 201 | 202 | ▸ **download**(`opt?`): `void` 203 | 204 | #### Parameters 205 | 206 | | Name | Type | 207 | | :------ | :------ | 208 | | `opt?` | [`DownloadOption`](../../modules/svg_drawing_core.md#downloadoption) | 209 | 210 | #### Returns 211 | 212 | `void` 213 | 214 | #### Defined in 215 | 216 | [core/src/drawing.ts:217](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L217) 217 | 218 | ___ 219 | 220 | ### drawEnd 221 | 222 | ▸ **drawEnd**(): `void` 223 | 224 | #### Returns 225 | 226 | `void` 227 | 228 | #### Defined in 229 | 230 | [core/src/drawing.ts:169](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L169) 231 | 232 | ___ 233 | 234 | ### drawMove 235 | 236 | ▸ **drawMove**(`po`): `void` 237 | 238 | #### Parameters 239 | 240 | | Name | Type | 241 | | :------ | :------ | 242 | | `po` | [`PointObject`](../../modules/svg_drawing_core.md#pointobject) | 243 | 244 | #### Returns 245 | 246 | `void` 247 | 248 | #### Defined in 249 | 250 | [core/src/drawing.ts:154](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L154) 251 | 252 | ___ 253 | 254 | ### drawStart 255 | 256 | ▸ **drawStart**(): `void` 257 | 258 | #### Returns 259 | 260 | `void` 261 | 262 | #### Defined in 263 | 264 | [core/src/drawing.ts:148](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L148) 265 | 266 | ___ 267 | 268 | ### off 269 | 270 | ▸ **off**(): `void` 271 | 272 | #### Returns 273 | 274 | `void` 275 | 276 | #### Defined in 277 | 278 | [core/src/drawing.ts:143](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L143) 279 | 280 | ___ 281 | 282 | ### on 283 | 284 | ▸ **on**(): `void` 285 | 286 | #### Returns 287 | 288 | `void` 289 | 290 | #### Defined in 291 | 292 | [core/src/drawing.ts:138](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L138) 293 | 294 | ___ 295 | 296 | ### undo 297 | 298 | ▸ **undo**(): `undefined` \| [`Path`](Path.md) 299 | 300 | #### Returns 301 | 302 | `undefined` \| [`Path`](Path.md) 303 | 304 | #### Defined in 305 | 306 | [core/src/drawing.ts:126](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L126) 307 | 308 | ___ 309 | 310 | ### update 311 | 312 | ▸ **update**(): `void` 313 | 314 | #### Returns 315 | 316 | `void` 317 | 318 | #### Defined in 319 | 320 | [core/src/drawing.ts:115](https://github.com/kmkzt/svg-drawing/blob/c168ec0/packages/core/src/drawing.ts#L115) 321 | -------------------------------------------------------------------------------- /packages/core/src/handler.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | DrawHandlerCallback, 3 | ResizeHandlerCallback, 4 | ListenerMaps, 5 | DrawListenerType, 6 | } from './types' 7 | 8 | export const getPassiveOptions = ( 9 | passive = true 10 | ): boolean | { passive: boolean } => { 11 | try { 12 | const check = () => null 13 | addEventListener('testPassive', check, { passive }) 14 | removeEventListener('testPassive', check) 15 | return { passive } 16 | } catch (e) { 17 | return false 18 | } 19 | } 20 | 21 | const listenerMaps: ListenerMaps = { 22 | pointer: { 23 | start: ['pointerdown'], 24 | move: ['pointermove'], 25 | end: ['pointerleave', 'pointercancel'], 26 | frameout: ['pointerup'], 27 | }, 28 | touch: { 29 | start: ['touchstart'], 30 | move: ['touchmove'], 31 | end: ['touchend'], 32 | frameout: ['touchcancel'], 33 | }, 34 | mouse: { 35 | start: ['mousedown'], 36 | move: ['mousemove'], 37 | end: ['mouseleave', 'mouseout'], 38 | frameout: ['mouseup'], 39 | }, 40 | } 41 | 42 | export class DrawHandler { 43 | /** Remove EventList */ 44 | private _clearEventList: Array<() => void> 45 | /** AddEventListener Options */ 46 | private _listenerOption: ReturnType 47 | /** Offset coordinates */ 48 | private _left: number 49 | private _top: number 50 | /** EventHandler */ 51 | public end: DrawHandlerCallback['end'] 52 | public start: DrawHandlerCallback['start'] 53 | public move: DrawHandlerCallback['move'] 54 | constructor( 55 | private _el: HTMLElement, 56 | { end, start, move }: DrawHandlerCallback 57 | ) { 58 | /** Bind property from arguments. */ 59 | this.end = end 60 | this.start = start 61 | this.move = move 62 | /** Setup property. */ 63 | this._clearEventList = [] 64 | this._listenerOption = getPassiveOptions(false) 65 | /** Set offset coordinates */ 66 | const { left, top } = _el.getBoundingClientRect() 67 | this._left = left 68 | this._top = top 69 | /** Bind mthods */ 70 | this._handleStart = this._handleStart.bind(this) 71 | this._handleMove = this._handleMove.bind(this) 72 | this._handleEnd = this._handleEnd.bind(this) 73 | } 74 | 75 | /** Exec removeEventListener */ 76 | public off() { 77 | this._clearEventList.map((fn) => fn()) 78 | this._clearEventList = [] 79 | } 80 | 81 | /** Exec addEventListener */ 82 | public on(): void { 83 | this.off() 84 | 85 | // Setup coordinates listener 86 | this._setupCoordinatesListener() 87 | 88 | // Setup Draw listener 89 | if (window.PointerEvent) { 90 | this._setupDrawListener('pointer') 91 | } else { 92 | this._setupDrawListener('mouse') 93 | } 94 | if ('ontouchstart' in window) { 95 | this._setupDrawListener('touch') 96 | } 97 | } 98 | 99 | private _handleStart(ev: TouchEvent | MouseEvent | PointerEvent) { 100 | ev.preventDefault() 101 | this.start() 102 | } 103 | 104 | private _handleEnd(ev: TouchEvent | MouseEvent | PointerEvent) { 105 | ev.preventDefault() 106 | this.end() 107 | } 108 | 109 | private _handleMove(ev: MouseEvent | PointerEvent | TouchEvent) { 110 | ev.preventDefault() 111 | if (ev instanceof TouchEvent) { 112 | const touch = ev.touches[0] 113 | this.move({ 114 | x: touch.clientX - this._left, 115 | y: touch.clientY - this._top, 116 | pressure: touch.force, 117 | }) 118 | return 119 | } 120 | if (ev instanceof PointerEvent) { 121 | this.move({ 122 | x: ev.clientX - this._left, 123 | y: ev.clientY - this._top, 124 | pressure: ev.pressure, 125 | }) 126 | return 127 | } 128 | if (ev instanceof MouseEvent) { 129 | this.move({ 130 | x: ev.clientX - this._left, 131 | y: ev.clientY - this._top, 132 | pressure: (ev as any)?.pressure, 133 | }) 134 | return 135 | } 136 | } 137 | 138 | private _setupDrawListener(type: DrawListenerType): void { 139 | const { start, move, end, frameout } = listenerMaps[type] 140 | const startClear = start.map((evname): (() => void) => { 141 | this._el.addEventListener(evname, this._handleStart, this._listenerOption) 142 | return () => this._el.removeEventListener(evname, this._handleStart) 143 | }) 144 | const moveClear = move.map((evname): (() => void) => { 145 | this._el.addEventListener(evname, this._handleMove, this._listenerOption) 146 | return () => this._el.removeEventListener(evname, this._handleMove) 147 | }) 148 | const endClear = end.map((evname): (() => void) => { 149 | this._el.addEventListener(evname, this._handleEnd, this._listenerOption) 150 | return () => this._el.removeEventListener(evname, this._handleEnd) 151 | }) 152 | const frameoutClear = frameout.map((evname): (() => void) => { 153 | addEventListener(evname, this._handleEnd, this._listenerOption) 154 | return () => removeEventListener(evname, this._handleEnd) 155 | }) 156 | this._clearEventList.push( 157 | ...startClear, 158 | ...moveClear, 159 | ...endClear, 160 | ...frameoutClear 161 | ) 162 | } 163 | 164 | private _setupCoordinatesListener() { 165 | const handleEvent = (_ev: Event) => { 166 | const { left, top } = this._el.getBoundingClientRect() 167 | this._left = left 168 | this._top = top 169 | } 170 | addEventListener('scroll', handleEvent) 171 | this._el.addEventListener('resize', handleEvent) 172 | this._clearEventList.push(() => { 173 | removeEventListener('scroll', handleEvent) 174 | this._el.removeEventListener('resize', handleEvent) 175 | }) 176 | } 177 | } 178 | 179 | export class ResizeHandler { 180 | /** Remove EventList */ 181 | private _clearEventList: Array<() => void> 182 | public resize: ResizeHandlerCallback['resize'] 183 | constructor(private _el: HTMLElement, { resize }: ResizeHandlerCallback) { 184 | this.resize = resize 185 | this._clearEventList = [] 186 | } 187 | 188 | public off() { 189 | this._clearEventList.map((fn) => fn()) 190 | this._clearEventList = [] 191 | } 192 | public on() { 193 | this.off() 194 | this._setupListerner() 195 | } 196 | 197 | private _setupListerner(): void { 198 | if ((window as any).ResizeObserver) { 199 | const resizeObserver: any = new (window as any).ResizeObserver( 200 | ([entry]: any[]) => { 201 | this.resize(entry.contentRect) 202 | } 203 | ) 204 | resizeObserver.observe(this._el) 205 | this._clearEventList.push(() => resizeObserver.disconnect()) 206 | } else { 207 | const handleResizeEvent = () => { 208 | this.resize(this._el.getBoundingClientRect()) 209 | } 210 | addEventListener('resize', handleResizeEvent) 211 | this._clearEventList.push(() => 212 | removeEventListener('resize', handleResizeEvent) 213 | ) 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /examples/react/pages/demo/animation.tsx: -------------------------------------------------------------------------------- 1 | import { SvgAnimation } from '@svg-drawing/animation' 2 | import { Point } from '@svg-drawing/core' 3 | import { useEffect, useRef, useCallback, useState } from 'react' 4 | import { Box, Flex, Button, Text } from 'rebass/styled-components' 5 | import Layout from '../../components/Layout' 6 | import { example } from '../../lib/example-svg' 7 | import type { FrameAnimation } from '@svg-drawing/animation' 8 | import type { Command } from '@svg-drawing/core' 9 | import type { NextPage } from 'next' 10 | import type { ChangeEvent } from 'react' 11 | const size = 30 12 | const shake: FrameAnimation = (paths) => { 13 | const range = 5 14 | const randomShaking = (): number => Math.random() * range - range / 2 15 | for (let i = 0; i < paths.length; i += 1) { 16 | paths[i].commands = paths[i].commands.map((c: Command) => { 17 | c.point = c.point?.add(new Point(randomShaking(), randomShaking())) 18 | c.cl = c.cl?.add(new Point(randomShaking(), randomShaking())) 19 | c.cr = c.cr?.add(new Point(randomShaking(), randomShaking())) 20 | return c 21 | }) 22 | } 23 | return paths 24 | } 25 | 26 | const colorfulList = [ 27 | '#F44336', 28 | '#E91E63', 29 | '#9C27B0', 30 | '#673AB7', 31 | '#3F51B5', 32 | '#2196F3', 33 | '#00BCD4', 34 | '#009688', 35 | '#4CAF50', 36 | '#8BC34A', 37 | '#CDDC39', 38 | '#FFEB3B', 39 | '#FFC107', 40 | '#FF9800', 41 | '#FF5722', 42 | ] 43 | 44 | const colorfulAnimation: FrameAnimation = (paths, fid) => { 45 | if (!fid) return paths 46 | for (let i = 0; i < paths.length; i += 1) { 47 | paths[i].attrs.stroke = colorfulList[fid % colorfulList.length] 48 | paths[i].attrs.fill = colorfulList[(fid + 4) % colorfulList.length] 49 | } 50 | return paths 51 | } 52 | 53 | const drawingAnimation: FrameAnimation = (paths, fid) => { 54 | if (!fid) return paths 55 | const update = [] 56 | for (let i = 0; i < paths.length; i += 1) { 57 | if (fid < paths[i].commands.length) { 58 | paths[i].commands = paths[i].commands.slice(0, fid) 59 | update.push(paths[i]) 60 | break 61 | } 62 | fid -= paths[i].commands.length 63 | update.push(paths[i]) 64 | } 65 | return update 66 | } 67 | 68 | interface Props { 69 | isSp: boolean 70 | } 71 | const Animation: NextPage = ({ isSp }) => { 72 | const aniDivRef = useRef(null) 73 | const animationRef = useRef(null) 74 | const [animMs, setAnimMs] = useState(20) 75 | 76 | const handleChangeAnimMs = useCallback((e: ChangeEvent) => { 77 | if (!animationRef.current) return 78 | const num = Number(e.target.value) 79 | if (Number.isNaN(num)) return 80 | animationRef.current.ms = num 81 | setAnimMs(num) 82 | }, []) 83 | 84 | useEffect(() => { 85 | if (animationRef.current) return 86 | if (!aniDivRef.current) return 87 | animationRef.current = new SvgAnimation(aniDivRef.current, { 88 | ms: animMs, 89 | background: '#fff', 90 | }) 91 | 92 | // SET EXAMPLE 93 | animationRef.current.svg.parseSVGString(example) 94 | handleDrawingAnimation() 95 | }) 96 | 97 | const handleFiles = useCallback((e: ChangeEvent) => { 98 | const reader = new FileReader() 99 | reader.onload = function (ev: ProgressEvent) { 100 | if (!ev.target || typeof ev.target.result !== 'string') return 101 | const [type, data] = ev.target.result.split(',') 102 | if (type === 'data:image/svg+xml;base64') { 103 | const svgxml = atob(data) 104 | if (!animationRef.current) return 105 | animationRef.current.svg.parseSVGString(svgxml) 106 | animationRef.current.update() 107 | } 108 | } 109 | if (!e.target?.files) return 110 | reader.readAsDataURL(e.target.files[0]) 111 | }, []) 112 | const handleShake = useCallback(() => { 113 | if (!animationRef.current) return 114 | animationRef.current.setAnimation(shake, { 115 | frames: 3, 116 | }) 117 | animationRef.current.start() 118 | }, []) 119 | const handleDrawingAnimation = useCallback(() => { 120 | if (!animationRef.current) return 121 | animationRef.current.setAnimation(drawingAnimation, { 122 | repeatCount: 1, 123 | }) 124 | animationRef.current.start() 125 | }, []) 126 | const handleColorfulAnimation = useCallback(() => { 127 | if (!animationRef.current) return 128 | animationRef.current.setAnimation(colorfulAnimation, { 129 | frames: colorfulList.length, 130 | }) 131 | animationRef.current.start() 132 | }, []) 133 | const handleStop = useCallback(() => { 134 | if (!animationRef.current) return 135 | animationRef.current.stop() 136 | }, []) 137 | const handleRestore = useCallback(() => { 138 | if (!animationRef.current) return 139 | animationRef.current.restore() 140 | }, []) 141 | const handleDownloadAnimation = useCallback(() => { 142 | if (!animationRef.current) return 143 | animationRef.current.download() 144 | }, []) 145 | return ( 146 | 147 | 148 | 149 | 160 | 168 | 169 | 170 | 171 | 174 | 182 | 190 | 193 | 196 | 204 | 205 | 206 | 207 | Svg exported by this library can be read. 208 | 209 | 210 | 211 | 212 |
222 | 223 | 224 | ) 225 | } 226 | 227 | Animation.getInitialProps = ({ req }) => { 228 | const ua = req ? req.headers['user-agent'] : navigator.userAgent 229 | return { 230 | isSp: ua ? /iPhone|Android.+Mobile/.test(ua) : true, 231 | } 232 | } 233 | 234 | export default Animation 235 | -------------------------------------------------------------------------------- /packages/animation/src/animation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | downloadBlob, 3 | svg2base64, 4 | ResizeHandler, 5 | Renderer, 6 | createSvgElement, 7 | createSvgChildElement, 8 | pathObjectToElement, 9 | Svg, 10 | camel2kebab, 11 | roundUp, 12 | } from '@svg-drawing/core' 13 | import type { AnimationOption, FrameAnimation } from './types' 14 | import type { Path, ResizeHandlerCallback } from '@svg-drawing/core' 15 | 16 | export class SvgAnimation { 17 | /** Options */ 18 | public ms: number 19 | /** Private prorperty */ 20 | private _stopId: number 21 | private _stopAnimation: (() => void) | null 22 | private _anim: FrameAnimation | null 23 | private _restorePaths: Path[] 24 | private _framesNumber: number | undefined 25 | /** Modules */ 26 | public svg: Svg 27 | public renderer: Renderer 28 | public resizeHandler: ResizeHandler 29 | /** Relation animate element */ 30 | private _repeatCount: string 31 | constructor( 32 | el: HTMLElement, 33 | { background, ms }: AnimationOption = { ms: 60 } 34 | ) { 35 | this.ms = ms 36 | this._stopAnimation = null 37 | this._anim = null 38 | this._restorePaths = [] 39 | this._stopId = 0 40 | this._repeatCount = 'indefinite' 41 | /** Setup Svg */ 42 | const { width, height } = el.getBoundingClientRect() 43 | this.svg = new Svg({ width, height, background }) 44 | /** Setup renderer */ 45 | this.renderer = new Renderer(el, { background }) 46 | /** Setup resize handler */ 47 | this._resize = this._resize.bind(this) 48 | this.resizeHandler = new ResizeHandler(el, { 49 | resize: this._resize, 50 | }) 51 | this.resizeHandler.on() 52 | } 53 | 54 | /** 55 | * @param {FramaAnimation} fn 56 | * @param {{ frame?: number; repeat?: number }} opts `frame` is the number of 57 | * frames to animate `repeat` is related for repeatCount of animate element 58 | * attribute. 59 | */ 60 | public setAnimation( 61 | fn: FrameAnimation, 62 | { 63 | frames, 64 | repeatCount, 65 | ms, 66 | }: { frames?: number; repeatCount?: number | string; ms?: number } = {} 67 | ): void { 68 | this._anim = fn 69 | this._framesNumber = frames 70 | this._repeatCount = repeatCount ? `${repeatCount}` : 'indefinite' 71 | if (ms) this.ms = ms 72 | } 73 | 74 | public stop(): boolean { 75 | if (this._stopAnimation) { 76 | this._stopAnimation() 77 | this.restore() 78 | return true 79 | } 80 | return false 81 | } 82 | 83 | public restore(): void { 84 | this.svg.paths = this._restorePaths 85 | this.update() 86 | } 87 | 88 | public generateFrame(index?: number): Path[] { 89 | if (!this._anim) return this.svg.paths 90 | return this._anim( 91 | this._restorePaths.map((p) => p.clone()), 92 | index 93 | ) 94 | } 95 | 96 | public start(): void { 97 | // If do not this first, this cannot get the number of frames well. 98 | this.stop() 99 | this._registerRestorePaths() 100 | this._startAnimation() 101 | } 102 | 103 | private _startAnimation(): void { 104 | let index = 0 105 | let start: number | undefined 106 | const ms = this.ms 107 | const loopCount: number = this._getFramesNumber() 108 | const frame: FrameRequestCallback = (timestamp) => { 109 | if (ms !== this.ms) { 110 | this.restore() 111 | this.start() 112 | return 113 | } 114 | if (!start || timestamp - start > ms) { 115 | start = timestamp 116 | this.svg.paths = this.generateFrame(index) 117 | this.update() 118 | index = index > loopCount ? 0 : index + 1 119 | } 120 | this._stopId = requestAnimationFrame(frame) 121 | } 122 | this._stopId = requestAnimationFrame(frame) 123 | this._stopAnimation = () => { 124 | cancelAnimationFrame(this._stopId) 125 | this._stopAnimation = null 126 | } 127 | } 128 | 129 | public update() { 130 | this.renderer.update(this.svg.toJson()) 131 | } 132 | 133 | public toElement(): SVGSVGElement { 134 | // If the animation is stopped, read the currently displayed Svg data. 135 | // If stopped in the middle, SVG in that state is displayed 136 | if (!this._stopAnimation) { 137 | this._registerRestorePaths() 138 | } 139 | 140 | const loopNumber = this._getFramesNumber() 141 | const animPathsList: Path[][] = Array(loopNumber) 142 | .fill(null) 143 | .map((_: any, i: number) => this.generateFrame(i)) 144 | 145 | const dur = loopNumber * (this.ms > 0 ? this.ms : 1) + 'ms' 146 | const t = 1 / loopNumber 147 | const keyTimes = `0;${Array(loopNumber) 148 | .fill(undefined) 149 | .map((_, i) => roundUp((i + 1) * t, 4) + '') 150 | .join(';')}` 151 | 152 | const createAnimationElement = ( 153 | origin: Path, 154 | attributeName: string, 155 | defaultValue: string, 156 | getValue: ({ 157 | origin, 158 | path, 159 | }: { 160 | origin: Path 161 | path?: Path 162 | }) => string | undefined 163 | ): SVGElement | null => { 164 | const animValues = animPathsList.map((ap) => { 165 | const path = ap.find((p) => p.attrs.id === origin.attrs.id) 166 | return getValue({ origin, path }) || defaultValue 167 | }) 168 | // return null if value is same all. 169 | if (animValues.every((v) => v === defaultValue)) return null 170 | 171 | return createSvgChildElement('animate', { 172 | dur, 173 | keyTimes, 174 | attributeName, 175 | repeatCount: this._repeatCount, 176 | values: [defaultValue, ...animValues].join(';'), 177 | }) 178 | } 179 | const animEls = this._restorePaths.map((p) => { 180 | const pEl = pathObjectToElement(p.toJson()) 181 | const dAnimEl = createAnimationElement( 182 | p, 183 | 'd', 184 | p.getCommandString(), 185 | ({ origin, path }) => 186 | path && path.commands.length > 0 187 | ? path.getCommandString() 188 | : origin.commands[0].toString() 189 | ) 190 | if (dAnimEl) pEl.appendChild(dAnimEl) 191 | 192 | // TODO: Check attribute key and value. 193 | const { id, ...attrs } = p.attrs // exclude id 194 | Object.entries(attrs).map( 195 | ([propertyName, val]: [string, string | undefined]) => { 196 | if (!val) return 197 | const attrName = camel2kebab(propertyName) 198 | const aEl = createAnimationElement( 199 | p, 200 | attrName, 201 | val, 202 | ({ path }) => path?.attrs[propertyName] 203 | ) 204 | if (aEl) pEl.appendChild(aEl) 205 | } 206 | ) 207 | 208 | return pEl 209 | }) 210 | 211 | const size = { 212 | width: String(this.svg.width), 213 | height: String(this.svg.height), 214 | } 215 | const bgEl = this.svg.background 216 | ? [ 217 | createSvgChildElement('rect', { 218 | ...size, 219 | fill: this.svg.background, 220 | }), 221 | ] 222 | : [] 223 | return createSvgElement( 224 | { 225 | width: String(this.svg.width), 226 | height: String(this.svg.height), 227 | }, 228 | bgEl.concat(animEls) 229 | ) 230 | } 231 | 232 | /** @param filename */ 233 | public download(filename?: string): void { 234 | downloadBlob({ 235 | data: svg2base64(this.toElement().outerHTML), 236 | extension: 'svg', 237 | filename, 238 | }) 239 | } 240 | 241 | /** @returns {number} Default value is total of commands length. */ 242 | private _getFramesNumber(): number { 243 | return this._framesNumber && this._framesNumber > 0 244 | ? this._framesNumber 245 | : this._restorePaths.reduce((l, p) => l + p.commands.length, 0) 246 | } 247 | 248 | private _registerRestorePaths() { 249 | this._restorePaths = this.svg.clonePaths().map((p, i) => { 250 | p.attrs.id = `t${i}` 251 | return p 252 | }) 253 | } 254 | 255 | private _resize({ 256 | width, 257 | height, 258 | }: Parameters[0]): void { 259 | this.stop() 260 | this.svg.resize({ width, height }) 261 | this.start() 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /examples/react/pages/demo/img-trace.tsx: -------------------------------------------------------------------------------- 1 | import { Renderer, download } from '@svg-drawing/core' 2 | import { 3 | ImgTrace, 4 | Palette, 5 | // Blur, 6 | ImgLoader, 7 | } from '@svg-drawing/img-trace' 8 | import { useState, useCallback, useRef, useEffect } from 'react' 9 | import { Box, Flex, Button, Image, Heading } from 'rebass/styled-components' 10 | import Layout from '../../components/Layout' 11 | import { basePath } from '../../config/paths' 12 | import type { Svg } from '@svg-drawing/core' 13 | import type { Rgba } from '@svg-drawing/img-trace' 14 | import type { ChangeEvent, RefObject } from 'react' 15 | 16 | const IMAGE_LIST = [ 17 | '/img_trace/cat.jpg', 18 | '/img_trace/harinezumi.jpg', 19 | '/img_trace/kuma.jpg', 20 | '/img_trace/panda.png', 21 | '/img_trace/risu.jpg', 22 | '/img_trace/tanuki.jpg', 23 | ].map((url) => `${basePath}${url}`) 24 | 25 | const GRAYSCALE_PALETTE = [ 26 | { r: 0, g: 0, b: 0, a: 255 }, 27 | { r: 50, g: 50, b: 50, a: 255 }, 28 | { r: 100, g: 100, b: 100, a: 255 }, 29 | { r: 150, g: 150, b: 150, a: 255 }, 30 | { r: 200, g: 200, b: 200, a: 255 }, 31 | ] 32 | export default () => { 33 | const [list, setList] = useState(IMAGE_LIST) 34 | const [palettes, setPalettes] = useState(GRAYSCALE_PALETTE) 35 | const [imageData, setImageData] = useState() 36 | const [paletteOption] = useState({ 37 | numberOfColors: 8, 38 | colorQuantCycles: 3, 39 | }) 40 | // const [blurOption] = useState({ radius: 20, delta: 0 }) 41 | const [traceOption] = useState({}) 42 | const [imageUrl, setImageUrl] = useState(IMAGE_LIST[0]) 43 | const [inputUrl, setInputUrl] = useState('') 44 | const [svg, setSvg] = useState() 45 | const imgRef: RefObject = useRef(null) 46 | // const canvasRef: RefObject = useRef(null) 47 | const renderRef = useRef(null) 48 | const handleInputUrl = useCallback( 49 | (e: ChangeEvent) => { 50 | setInputUrl(e.target.value) 51 | }, 52 | [setInputUrl] 53 | ) 54 | 55 | const createPalette = useCallback(() => { 56 | if (!imageData) return 57 | setPalettes(Palette.imageData(imageData, paletteOption)) 58 | }, [paletteOption, setPalettes, imageData]) 59 | 60 | const resetPalette = useCallback(() => { 61 | setPalettes(GRAYSCALE_PALETTE) 62 | }, [setPalettes]) 63 | 64 | const deleteColor = useCallback( 65 | (di: number) => () => { 66 | const update = palettes.filter((_p, i) => i !== di) 67 | setPalettes(update) 68 | }, 69 | [setPalettes, palettes] 70 | ) 71 | const traceImage = useCallback(async () => { 72 | try { 73 | const imgd = 74 | imageData || 75 | (await new ImgLoader({ corsenabled: true }).fromUrl(imageUrl)) 76 | if (!imageData && imgd) { 77 | setImageData(imgd) 78 | } 79 | if (!imgd) return 80 | const trace = new ImgTrace({ ...traceOption, palettes }) 81 | const svg = trace.load(imgd) 82 | setSvg(svg) 83 | if (trace.palettes) setPalettes(trace.palettes) 84 | } catch (err) { 85 | // throw err 86 | } 87 | }, [imageUrl, imageData, palettes, traceOption]) 88 | 89 | /** Update Svg Render */ 90 | useEffect(() => { 91 | if (!renderRef.current) return 92 | const renderSvg = () => { 93 | if (!renderRef.current || !svg) return 94 | const renderer = new Renderer(renderRef.current) 95 | const { width, height } = renderRef.current.getBoundingClientRect() 96 | svg.resize({ width, height }) 97 | renderer.update(svg.toJson()) 98 | } 99 | renderSvg() 100 | window.addEventListener('resize', renderSvg) 101 | return () => window.removeEventListener('resize', renderSvg) 102 | }, [svg]) 103 | 104 | // const blurImage = useCallback(async () => { 105 | // try { 106 | // const imgd = 107 | // imageData || 108 | // (await new ImgLoader({ corsenabled: true }).fromUrl(imageUrl)) 109 | // if (!imageData && imgd) { 110 | // setImageData(imgd) 111 | // } 112 | // if (!imgd) return 113 | // const blurImage = new Blur(blurOption).apply(imgd) 114 | // setImageData(blurImage) 115 | // if (canvasRef.current) { 116 | // canvasRef.current.width = blurImage.width 117 | // canvasRef.current.height = blurImage.height 118 | 119 | // const ctx = canvasRef.current.getContext('2d') 120 | // ctx?.putImageData( 121 | // blurImage, 122 | // 0, 123 | // 0, 124 | // 0, 125 | // 0, 126 | // blurImage.width, 127 | // blurImage.height 128 | // ) 129 | // } 130 | // } catch (err) { 131 | // // throw err 132 | // } 133 | // }, [imageData, blurOption, imageUrl]) 134 | const handleSelect = useCallback( 135 | (url: string) => () => { 136 | setImageUrl(url) 137 | if (!list.includes(url)) { 138 | setList([...list, url]) 139 | } 140 | }, 141 | [setImageUrl, list] 142 | ) 143 | 144 | const handleDownload = useCallback(() => { 145 | if (!svg) return 146 | download(svg) 147 | }, [svg]) 148 | 149 | useEffect(() => { 150 | if (!imgRef.current) return 151 | imgRef.current.onload = () => { 152 | if (!imgRef.current) return 153 | new ImgLoader({ corsenabled: true }).fromImageElement( 154 | imgRef.current, 155 | setImageData 156 | ) 157 | } 158 | }, [setImageData]) 159 | return ( 160 | 161 | 162 | 163 | 166 | 169 | 170 | {palettes 171 | .sort((p1: Rgba, p2: Rgba) => 172 | p1.r + p1.g + p1.b > p2.r + p2.g + p2.b ? -1 : 1 173 | ) 174 | .map((pal: Rgba, i) => ( 175 |
187 |
201 | x 202 |
203 |
204 | ))} 205 | 206 | 207 | 208 | {/* */} 209 | 212 | {svg && ( 213 | 216 | )} 217 | 218 | 222 | 229 | 230 | {/* 231 | 232 | */} 233 | 237 |
238 | 239 | 240 | 241 | 242 | 243 | Select Image 244 | 245 | 251 | 252 | 253 | 254 | {list.map((l, i) => ( 255 |
256 | {l} 257 |
258 | ))} 259 |
260 |
261 | 262 | ) 263 | } 264 | -------------------------------------------------------------------------------- /examples/react/lib/example-svg.ts: -------------------------------------------------------------------------------- 1 | export const example = `` 2 | --------------------------------------------------------------------------------