diff --git a/assets/data/toolsNav.ts b/assets/data/toolsNav.ts index 1601d657..f0c8a019 100644 --- a/assets/data/toolsNav.ts +++ b/assets/data/toolsNav.ts @@ -24,6 +24,11 @@ export default [ description: "_shareLinkGenerator.description", to: "/tools/share-link-generator/", }, + { + i18n: '_identiconGenerator.title', + description: '_identiconGenerator.description', + to: '/tools/identicon-generator/', + }, ], }, { diff --git a/assets/js/mi/gen-identicon.ts b/assets/js/mi/gen-identicon.ts new file mode 100644 index 00000000..434fb883 --- /dev/null +++ b/assets/js/mi/gen-identicon.ts @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * Identicon generator + * https://en.wikipedia.org/wiki/Identicon + */ + +import gen from 'random-seed'; + +const size = 128; // px +const n = 5; // resolution +const margin = (size / 4); +const colors = [ + ['#FF512F', '#DD2476'], + ['#FF61D2', '#FE9090'], + ['#72FFB6', '#10D164'], + ['#FD8451', '#FFBD6F'], + ['#305170', '#6DFC6B'], + ['#00C0FF', '#4218B8'], + ['#009245', '#FCEE21'], + ['#0100EC', '#FB36F4'], + ['#FDABDD', '#374A5A'], + ['#38A2D7', '#561139'], + ['#121C84', '#8278DA'], + ['#5761B2', '#1FC5A8'], + ['#FFDB01', '#0E197D'], + ['#FF3E9D', '#0E1F40'], + ['#766eff', '#00d4ff'], + ['#9bff6e', '#00d4ff'], + ['#ff6e94', '#00d4ff'], + ['#ffa96e', '#00d4ff'], + ['#ffa96e', '#ff009d'], + ['#ffdd6e', '#ff009d'], +]; + +const actualSize = size - (margin * 2); +const cellSize = actualSize / n; +const sideN = Math.floor(n / 2); + +/** + * canvasにIdenticonを描画する + * @param seed アカウントのacct + */ +export function genIdenticon(seed: string, canvas: HTMLCanvasElement) { + const rand = gen.create(seed); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext('2d'); + + if (!ctx) return; + + const bgColors = colors[rand(colors.length)]; + + const bg = ctx.createLinearGradient(0, 0, size, size); + bg.addColorStop(0, bgColors[0]); + bg.addColorStop(1, bgColors[1]); + + ctx.fillStyle = bg as any; + ctx.beginPath(); + ctx.fillRect(0, 0, size, size); + + ctx.fillStyle = '#ffffff'; + + // side bitmap (filled by false) + const side: boolean[][] = new Array(sideN); + for (let i = 0; i < side.length; i++) { + side[i] = new Array(n).fill(false); + } + + // 1*n (filled by false) + const center: boolean[] = new Array(n).fill(false); + + for (let x = 0; x < side.length; x++) { + for (let y = 0; y < side[x].length; y++) { + side[x][y] = rand(3) === 0; + } + } + + for (let i = 0; i < center.length; i++) { + center[i] = rand(3) === 0; + } + + // Draw + for (let x = 0; x < n; x++) { + for (let y = 0; y < n; y++) { + const isXCenter = x === ((n - 1) / 2); + if (isXCenter && !center[y]) continue; + + const isLeftSide = x < ((n - 1) / 2); + if (isLeftSide && !side[x][y]) continue; + + const isRightSide = x > ((n - 1) / 2); + if (isRightSide && !side[sideN - (x - sideN)][y]) continue; + + const actualX = margin + (cellSize * x); + const actualY = margin + (cellSize * y); + ctx.beginPath(); + ctx.fillRect(actualX, actualY, cellSize, cellSize); + } + } +} diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 4f1e2b8a..2146c0fa 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -7,6 +7,8 @@ share: "共有する" note: "ノート" other: "その他" add: "追加" +generate: "生成" +download: "ダウンロード" browse: "参照" settings: "設定" goToLegacyHub: "従来のMisskey Hub" @@ -336,6 +338,12 @@ _customEmojiPreview: _placeholder: noteText: "カスタム絵文字はこんな感じで表示されます→ :emoji_preview_1:\n文章を書き換えて、使い勝手を試してみてくださいね✨" +_identiconGenerator: + title: "初期アイコンジェネレーター" + description: "Misskeyに登録した際にデフォルトで指定される初期アイコンを生成できます。" + userName: "ユーザー名" + includeDomain: "サーバーのドメイン名を含む完全なユーザー名を指定してください。" + _api: _permissions: title: "権限" diff --git a/package.json b/package.json index 3714cf2b..3b8a742b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/js-yaml": "^4.0.9", "@types/node": "^20.11.28", "@types/nprogress": "^0.2.3", + "@types/random-seed": "^0.3.5", "@types/ua-parser-js": "^0.7.39", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.5", "autoprefixer": "^10.4.18", @@ -29,6 +30,7 @@ "nprogress": "^0.2.0", "nuxt": "^3.11.0", "postcss": "^8.4.36", + "random-seed": "^0.3.0", "sass": "^1.72.0", "schema-dts": "^1.1.2", "sitemap": "^7.1.1", diff --git a/pages/tools/identicon-generator.vue b/pages/tools/identicon-generator.vue new file mode 100644 index 00000000..7bf5f2d0 --- /dev/null +++ b/pages/tools/identicon-generator.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 644dea07..70273446 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ devDependencies: '@types/nprogress': specifier: ^0.2.3 version: 0.2.3 + '@types/random-seed': + specifier: ^0.3.5 + version: 0.3.5 '@types/ua-parser-js': specifier: ^0.7.39 version: 0.7.39 @@ -70,6 +73,9 @@ devDependencies: postcss: specifier: ^8.4.36 version: 8.4.36 + random-seed: + specifier: ^0.3.0 + version: 0.3.0 sass: specifier: ^1.72.0 version: 1.72.0 @@ -2294,6 +2300,10 @@ packages: resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} dev: true + /@types/random-seed@0.3.5: + resolution: {integrity: sha512-CftxcDPAHgs0SLHU2dt+ZlDPJfGqLW3sZlC/ATr5vJDSe5tRLeOne7HMvCOJnFyF8e1U41wqzs3h6AMC613xtA==} + dev: true + /@types/resolve@1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: true @@ -4849,6 +4859,10 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true + /json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -6854,6 +6868,13 @@ packages: resolution: {integrity: sha512-yUUd5VTiFtcMEx0qFUxGAv5gbMc1un4RvEO1JZdP7ZUl/RHygZK6PknIKntmQRZxnMY3ZXD2ISaw1ij8GYW1yg==} dev: true + /random-seed@0.3.0: + resolution: {integrity: sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==} + engines: {node: '>= 0.6.0'} + dependencies: + json-stringify-safe: 5.0.1 + dev: true + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: