pixelweaver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.development +1 -0
- package/.github/workflows/ci.yml +22 -0
- package/.github/workflows/publish.yml +18 -0
- package/.prettierignore +5 -0
- package/.prettierrc +16 -0
- package/.python-version +1 -0
- package/.rlsbl/bases/.github/workflows/ci.yml +21 -0
- package/.rlsbl/bases/.github/workflows/publish.yml +18 -0
- package/.rlsbl/bases/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/bases/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/bases/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/bases/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/bases/CHANGELOG.md +5 -0
- package/.rlsbl/bases/LICENSE +21 -0
- package/.rlsbl/changes/.validated +1 -0
- package/.rlsbl/changes/0.1.0.jsonl +85 -0
- package/.rlsbl/changes/0.1.0.md +13 -0
- package/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/config.json +6 -0
- package/.rlsbl/hashes.json +14 -0
- package/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/releases/unreleased.toml +0 -0
- package/.rlsbl/releases/v0.1.0.toml +3 -0
- package/.rlsbl/version +1 -0
- package/.selfdoc/hashes/hashes.json +146 -0
- package/.strictcli/schema.json +227 -0
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +100 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/assets/icon.png +0 -0
- package/docs/_README.md +117 -0
- package/docs/cli-config.md +35 -0
- package/docs/cli-dev.md +21 -0
- package/docs/cli-diagnose.md +12 -0
- package/docs/cli-index.md +30 -0
- package/docs/cli-list.md +18 -0
- package/docs/cli-mcp.md +18 -0
- package/docs/cli-new.md +26 -0
- package/docs/cli-open.md +21 -0
- package/docs/cli-serve.md +21 -0
- package/docs/cli-stop.md +12 -0
- package/docs/gen-index.md +36 -0
- package/docs/index.md +13 -0
- package/docs/server-src-pixelweaver-__main__.md +12 -0
- package/docs/server-src-pixelweaver-autosave.md +12 -0
- package/docs/server-src-pixelweaver-bridge.md +12 -0
- package/docs/server-src-pixelweaver-cli.md +12 -0
- package/docs/server-src-pixelweaver-config.md +12 -0
- package/docs/server-src-pixelweaver-connections.md +12 -0
- package/docs/server-src-pixelweaver-main.md +12 -0
- package/docs/server-src-pixelweaver-mcp_bridge.md +12 -0
- package/docs/server-src-pixelweaver-mcp_drawing_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_export_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_frame_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_history_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_layer_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_lock.md +12 -0
- package/docs/server-src-pixelweaver-mcp_project_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_read_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_registry.md +12 -0
- package/docs/server-src-pixelweaver-mcp_resources.md +12 -0
- package/docs/server-src-pixelweaver-mcp_server.md +12 -0
- package/docs/server-src-pixelweaver-protocol.md +12 -0
- package/docs/server-src-pixelweaver-state.md +12 -0
- package/docs/server-src-pixelweaver-storage.md +12 -0
- package/docs/server-src-pixelweaver-websocket.md +12 -0
- package/docs/server-src-pixelweaver.md +12 -0
- package/e2e/app-launch.test.ts +35 -0
- package/e2e/menus.test.ts +26 -0
- package/e2e/tools.test.ts +27 -0
- package/e2e/undo-redo.test.ts +11 -0
- package/eslint.config.js +62 -0
- package/index.html +13 -0
- package/package.json +48 -0
- package/playwright.config.ts +19 -0
- package/plugins/builtin/.gitkeep +0 -0
- package/plugins/builtin/advanced-fill-tool.ts +146 -0
- package/plugins/builtin/circle-tool.ts +186 -0
- package/plugins/builtin/diamond-tool.ts +182 -0
- package/plugins/builtin/dither-tool.ts +186 -0
- package/plugins/builtin/drawing-primitives-plugin.ts +362 -0
- package/plugins/builtin/drawing-utils.test.ts +495 -0
- package/plugins/builtin/drawing-utils.ts +431 -0
- package/plugins/builtin/effects/blur.ts +97 -0
- package/plugins/builtin/effects/color-effects.ts +278 -0
- package/plugins/builtin/effects/flip.ts +83 -0
- package/plugins/builtin/effects/glow.ts +118 -0
- package/plugins/builtin/effects/outline.ts +110 -0
- package/plugins/builtin/effects/rotate.ts +357 -0
- package/plugins/builtin/effects/scale.ts +258 -0
- package/plugins/builtin/effects/shadow.ts +111 -0
- package/plugins/builtin/effects/sharpen.ts +102 -0
- package/plugins/builtin/effects.test.ts +715 -0
- package/plugins/builtin/eraser-tool.ts +23 -0
- package/plugins/builtin/eyedropper-tool.ts +83 -0
- package/plugins/builtin/fill-tool.ts +93 -0
- package/plugins/builtin/gradient-tool.ts +204 -0
- package/plugins/builtin/gradient.test.ts +142 -0
- package/plugins/builtin/importers/aseprite-importer-plugin.ts +174 -0
- package/plugins/builtin/importers/aseprite-parser.ts +497 -0
- package/plugins/builtin/importers/piskel-importer-plugin.ts +222 -0
- package/plugins/builtin/importers/sky-spec-plugin.ts +409 -0
- package/plugins/builtin/line-tool.ts +267 -0
- package/plugins/builtin/make-stroke-tool.ts +271 -0
- package/plugins/builtin/noise-dither.test.ts +151 -0
- package/plugins/builtin/noise-tool.ts +131 -0
- package/plugins/builtin/pattern-stamp-tool.ts +162 -0
- package/plugins/builtin/pencil-tool.ts +25 -0
- package/plugins/builtin/rect-tool.ts +179 -0
- package/plugins/builtin/selection-tool.ts +388 -0
- package/plugins/builtin/selection.test.ts +195 -0
- package/plugins/builtin/tool-plugins.test.ts +529 -0
- package/public/favicon.svg +7 -0
- package/public/sw.js +91 -0
- package/pyproject.toml +49 -0
- package/scripts/eslint-wave-a-list.sh +24 -0
- package/scripts/eslint-wave-a-status.sh +25 -0
- package/scripts/fix-index-signature-access.py +127 -0
- package/scripts/fix-unchecked-index.py +128 -0
- package/scripts/fix-unchecked-vars.py +127 -0
- package/scripts/fix-wave-a-bangs.py +36 -0
- package/scripts/fix-wave-a-templates.py +108 -0
- package/scripts/fix-wave-b-void-expr.py +167 -0
- package/scripts/generate-command-params.py +540 -0
- package/scripts/migrate-single-frame-to-multi.py +171 -0
- package/scripts/smoke-test.sh +77 -0
- package/selfdoc.json +10 -0
- package/server/README.md +0 -0
- package/server/src/pixelweaver/__init__.py +1 -0
- package/server/src/pixelweaver/__main__.py +4 -0
- package/server/src/pixelweaver/autosave.py +114 -0
- package/server/src/pixelweaver/bridge.py +127 -0
- package/server/src/pixelweaver/cli.py +199 -0
- package/server/src/pixelweaver/config.py +24 -0
- package/server/src/pixelweaver/connections.py +54 -0
- package/server/src/pixelweaver/main.py +271 -0
- package/server/src/pixelweaver/mcp_bridge.py +189 -0
- package/server/src/pixelweaver/mcp_drawing_tools.py +178 -0
- package/server/src/pixelweaver/mcp_export_tools.py +291 -0
- package/server/src/pixelweaver/mcp_frame_tools.py +423 -0
- package/server/src/pixelweaver/mcp_history_tools.py +106 -0
- package/server/src/pixelweaver/mcp_layer_tools.py +64 -0
- package/server/src/pixelweaver/mcp_lock.py +37 -0
- package/server/src/pixelweaver/mcp_project_tools.py +302 -0
- package/server/src/pixelweaver/mcp_read_tools.py +163 -0
- package/server/src/pixelweaver/mcp_registry.py +247 -0
- package/server/src/pixelweaver/mcp_resources.py +312 -0
- package/server/src/pixelweaver/mcp_server.py +234 -0
- package/server/src/pixelweaver/protocol.py +219 -0
- package/server/src/pixelweaver/state.py +267 -0
- package/server/src/pixelweaver/storage.py +293 -0
- package/server/src/pixelweaver/websocket.py +282 -0
- package/server/tests/__init__.py +0 -0
- package/server/tests/conftest.py +17 -0
- package/server/tests/test_api.py +96 -0
- package/server/tests/test_bridge.py +161 -0
- package/server/tests/test_health.py +9 -0
- package/server/tests/test_integration.py +86 -0
- package/server/tests/test_mcp_bridge.py +293 -0
- package/server/tests/test_mcp_lock.py +34 -0
- package/server/tests/test_mcp_registry.py +679 -0
- package/server/tests/test_mcp_resources.py +648 -0
- package/server/tests/test_protocol.py +125 -0
- package/server/tests/test_state.py +87 -0
- package/server/tests/test_storage.py +306 -0
- package/server/tests/test_websocket.py +275 -0
- package/src/App.svelte +107 -0
- package/src/app.css +215 -0
- package/src/lib/animation/AnimationPreviewPanel.svelte +667 -0
- package/src/lib/animation/animation-commands.test.ts +228 -0
- package/src/lib/animation/animation-commands.ts +540 -0
- package/src/lib/animation/animation-preview-panel-plugin.ts +25 -0
- package/src/lib/animation/animation-preview.svelte.ts +151 -0
- package/src/lib/animation/clipboard.ts +134 -0
- package/src/lib/animation/frame-model.svelte.ts +437 -0
- package/src/lib/animation/frame-model.test.ts +314 -0
- package/src/lib/animation/frame-selection.svelte.ts +77 -0
- package/src/lib/animation/frame-tags.svelte.ts +238 -0
- package/src/lib/animation/frame-tags.test.ts +136 -0
- package/src/lib/animation/import.test.ts +141 -0
- package/src/lib/animation/import.ts +112 -0
- package/src/lib/animation/spritesheet-export.test.ts +239 -0
- package/src/lib/animation/spritesheet-export.ts +314 -0
- package/src/lib/canvas/CanvasViewport.svelte +216 -0
- package/src/lib/canvas/canvas-init-plugin.ts +178 -0
- package/src/lib/canvas/canvas-renderer.ts +408 -0
- package/src/lib/canvas/canvas-state.svelte.ts +232 -0
- package/src/lib/canvas/canvas-state.test.ts +139 -0
- package/src/lib/canvas/input-handler.ts +221 -0
- package/src/lib/canvas/input-plugin.ts +150 -0
- package/src/lib/canvas/onion-skin.ts +94 -0
- package/src/lib/canvas/pixel-buffer.test.ts +249 -0
- package/src/lib/canvas/pixel-buffer.ts +151 -0
- package/src/lib/canvas/render-state.svelte.ts +18 -0
- package/src/lib/canvas/shape-preview-state.svelte.ts +36 -0
- package/src/lib/canvas/tile-mode.test.ts +53 -0
- package/src/lib/canvas/tile-mode.ts +92 -0
- package/src/lib/canvas/viewport-utils.ts +24 -0
- package/src/lib/canvas/zoom-utils.ts +31 -0
- package/src/lib/color/.gitkeep +0 -0
- package/src/lib/color/color-commands.ts +87 -0
- package/src/lib/color/color-state.svelte.ts +98 -0
- package/src/lib/color/color-state.test.ts +91 -0
- package/src/lib/color/color-utils.test.ts +220 -0
- package/src/lib/color/color-utils.ts +243 -0
- package/src/lib/color/palette-state.svelte.ts +127 -0
- package/src/lib/color/palette-state.test.ts +154 -0
- package/src/lib/color/palette.ts +79 -0
- package/src/lib/core/bootstrap.ts +66 -0
- package/src/lib/core/command-params.generated.ts +1549 -0
- package/src/lib/core/command-params.ts +20 -0
- package/src/lib/core/command-runner.ts +79 -0
- package/src/lib/core/commands.ts +134 -0
- package/src/lib/core/dispatcher.test.ts +548 -0
- package/src/lib/core/dispatcher.ts +361 -0
- package/src/lib/core/index.test.ts +7 -0
- package/src/lib/core/notification-state.svelte.ts +119 -0
- package/src/lib/core/plugin-api.ts +210 -0
- package/src/lib/core/plugin-discovery.test.ts +53 -0
- package/src/lib/core/plugin-discovery.ts +65 -0
- package/src/lib/core/plugin-loader.test.ts +159 -0
- package/src/lib/core/plugin-loader.ts +240 -0
- package/src/lib/core/plugin-types.ts +286 -0
- package/src/lib/core/registries.svelte.ts +74 -0
- package/src/lib/core/tool-options-state.svelte.ts +61 -0
- package/src/lib/dock/DockLayout.svelte +375 -0
- package/src/lib/dock/TabAddMenu.svelte +90 -0
- package/src/lib/dock/dock-persistence.ts +46 -0
- package/src/lib/dock/dock-plugin.ts +49 -0
- package/src/lib/dock/dock-presets.ts +156 -0
- package/src/lib/dock/dock-state.svelte.ts +77 -0
- package/src/lib/dock/dock-types.ts +2 -0
- package/src/lib/dock/dockview-theme.css +226 -0
- package/src/lib/dock/dockview-theme.ts +17 -0
- package/src/lib/dock/header-action-renderer.ts +77 -0
- package/src/lib/edit/clipboard-state.ts +34 -0
- package/src/lib/export/download.ts +201 -0
- package/src/lib/export/png-metadata.ts +181 -0
- package/src/lib/history/ActionLogPanel.svelte +418 -0
- package/src/lib/history/action-log-panel-plugin.ts +24 -0
- package/src/lib/history/action-log.svelte.ts +172 -0
- package/src/lib/history/action-log.test.ts +168 -0
- package/src/lib/history/history-commands.ts +403 -0
- package/src/lib/history/macros.svelte.ts +320 -0
- package/src/lib/history/macros.test.ts +224 -0
- package/src/lib/history/replay-engine.test.ts +241 -0
- package/src/lib/history/replay-engine.ts +149 -0
- package/src/lib/history/spec-format.test.ts +250 -0
- package/src/lib/history/spec-format.ts +210 -0
- package/src/lib/iso/SeamCheckerPanel.svelte +251 -0
- package/src/lib/iso/iso-math.test.ts +77 -0
- package/src/lib/iso/iso-math.ts +77 -0
- package/src/lib/iso/seam-checker-panel-plugin.ts +25 -0
- package/src/lib/iso/seam-checker.test.ts +126 -0
- package/src/lib/iso/seam-checker.ts +93 -0
- package/src/lib/iso/tessellation.ts +67 -0
- package/src/lib/layers/compositor.test.ts +193 -0
- package/src/lib/layers/compositor.ts +175 -0
- package/src/lib/layers/layer-commands.test.ts +263 -0
- package/src/lib/layers/layer-commands.ts +429 -0
- package/src/lib/layers/layer-tree.svelte.ts +516 -0
- package/src/lib/layers/layer-tree.test.ts +383 -0
- package/src/lib/layers/layer-types.ts +56 -0
- package/src/lib/leveleditor/LevelEditorViewport.svelte +1808 -0
- package/src/lib/leveleditor/MapPropertiesPanel.svelte +266 -0
- package/src/lib/leveleditor/TilePickerPanel.svelte +324 -0
- package/src/lib/leveleditor/depth-sort.test.ts +70 -0
- package/src/lib/leveleditor/depth-sort.ts +39 -0
- package/src/lib/leveleditor/level-editor-commands.ts +353 -0
- package/src/lib/leveleditor/level-editor-viewport-plugin.ts +25 -0
- package/src/lib/leveleditor/map-properties-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/map-state.svelte.ts +372 -0
- package/src/lib/leveleditor/map-state.test.ts +243 -0
- package/src/lib/leveleditor/tile-picker-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/tile-source-registry.svelte.ts +91 -0
- package/src/lib/leveleditor/tiled-export.test.ts +144 -0
- package/src/lib/leveleditor/tiled-export.ts +374 -0
- package/src/lib/pywebview.d.ts +15 -0
- package/src/lib/recovery/.gitkeep +0 -0
- package/src/lib/recovery/idb-store.ts +118 -0
- package/src/lib/recovery/recovery-manager.test.ts +321 -0
- package/src/lib/recovery/recovery-manager.ts +115 -0
- package/src/lib/recovery/recovery-plugin.ts +55 -0
- package/src/lib/recovery/recovery-state.svelte.ts +21 -0
- package/src/lib/recovery/sw-plugin.ts +18 -0
- package/src/lib/recovery/sw-register.ts +17 -0
- package/src/lib/save/directory-format.ts +42 -0
- package/src/lib/save/project-snapshot.ts +139 -0
- package/src/lib/save/recent-projects.ts +56 -0
- package/src/lib/save/save-state.svelte.ts +35 -0
- package/src/lib/save/storage.ts +167 -0
- package/src/lib/save/zip-format.ts +45 -0
- package/src/lib/shortcuts/.gitkeep +0 -0
- package/src/lib/shortcuts/ShortcutEditorPanel.svelte +690 -0
- package/src/lib/shortcuts/default-bindings.ts +61 -0
- package/src/lib/shortcuts/shortcut-editor-panel-plugin.ts +25 -0
- package/src/lib/shortcuts/shortcut-init.ts +380 -0
- package/src/lib/shortcuts/shortcut-manager.test.ts +466 -0
- package/src/lib/shortcuts/shortcut-manager.ts +281 -0
- package/src/lib/shortcuts/shortcut-state.svelte.ts +78 -0
- package/src/lib/shortcuts/shortcuts-plugin.ts +17 -0
- package/src/lib/sync/patch-applicator.ts +300 -0
- package/src/lib/sync/patch-types.ts +65 -0
- package/src/lib/sync/project-manager.ts +108 -0
- package/src/lib/sync/sync-init.ts +152 -0
- package/src/lib/sync/sync-plugin.ts +19 -0
- package/src/lib/sync/sync-state.svelte.ts +56 -0
- package/src/lib/sync/ws-client.test.ts +604 -0
- package/src/lib/sync/ws-client.ts +574 -0
- package/src/lib/tools/.gitkeep +0 -0
- package/src/lib/ui/.gitkeep +0 -0
- package/src/lib/ui/AboutDialog.svelte +113 -0
- package/src/lib/ui/ColorPicker.svelte +761 -0
- package/src/lib/ui/ContextMenu.svelte +216 -0
- package/src/lib/ui/ExportDialog.svelte +747 -0
- package/src/lib/ui/FrameStrip.svelte +854 -0
- package/src/lib/ui/LayerPanel.svelte +810 -0
- package/src/lib/ui/MenuBar.svelte +590 -0
- package/src/lib/ui/NewProjectDialog.svelte +803 -0
- package/src/lib/ui/PluginManagerPanel.svelte +475 -0
- package/src/lib/ui/PromptDialog.svelte +252 -0
- package/src/lib/ui/RecoveryDialog.svelte +295 -0
- package/src/lib/ui/ResizeDialog.svelte +416 -0
- package/src/lib/ui/StatusBar.svelte +145 -0
- package/src/lib/ui/ToolbarPanel.svelte +488 -0
- package/src/lib/ui/animation-menu-commands.ts +194 -0
- package/src/lib/ui/command-palette/CommandPalette.svelte +232 -0
- package/src/lib/ui/command-palette/command-palette-plugin.ts +30 -0
- package/src/lib/ui/command-palette/command-palette-state.svelte.ts +190 -0
- package/src/lib/ui/command-palette/command-palette.test.ts +129 -0
- package/src/lib/ui/dialog-state.svelte.ts +70 -0
- package/src/lib/ui/edit-commands.ts +271 -0
- package/src/lib/ui/file-commands.ts +275 -0
- package/src/lib/ui/file-open.ts +99 -0
- package/src/lib/ui/help-commands.ts +93 -0
- package/src/lib/ui/image-commands.ts +181 -0
- package/src/lib/ui/layer-menu-commands.ts +420 -0
- package/src/lib/ui/menu-builder.ts +224 -0
- package/src/lib/ui/notifications/NotificationBanner.svelte +137 -0
- package/src/lib/ui/notifications/notification-plugin.ts +29 -0
- package/src/lib/ui/notifications/notification-state.svelte.ts +9 -0
- package/src/lib/ui/plugin-manager-panel-plugin.ts +26 -0
- package/src/lib/ui/plugin-state.svelte.ts +62 -0
- package/src/lib/ui/select-commands.ts +75 -0
- package/src/lib/ui/theme-plugin.ts +18 -0
- package/src/lib/ui/theme.svelte.ts +45 -0
- package/src/lib/ui/theme.test.ts +51 -0
- package/src/lib/ui/toolbar-config.ts +90 -0
- package/src/lib/ui/toolbar-plugin.ts +39 -0
- package/src/lib/ui/view-commands.ts +629 -0
- package/src/lib/variants/BisectionExportDialog.svelte +500 -0
- package/src/lib/variants/VariantPanel.svelte +822 -0
- package/src/lib/variants/bisection-export.test.ts +113 -0
- package/src/lib/variants/bisection-export.ts +148 -0
- package/src/lib/variants/palette-extraction.test.ts +111 -0
- package/src/lib/variants/palette-extraction.ts +84 -0
- package/src/lib/variants/palette-interpolation.test.ts +113 -0
- package/src/lib/variants/palette-interpolation.ts +87 -0
- package/src/lib/variants/palette-swap.test.ts +101 -0
- package/src/lib/variants/palette-swap.ts +114 -0
- package/src/lib/variants/variant-commands.ts +594 -0
- package/src/lib/variants/variant-panel-plugin.ts +27 -0
- package/src/lib/variants/variant-randomizer.ts +101 -0
- package/src/lib/variants/variant-state.svelte.ts +166 -0
- package/src/lib/variants/variant-state.test.ts +138 -0
- package/src/main.ts +14 -0
- package/src/vite-env.d.ts +3 -0
- package/svelte.config.js +2 -0
- package/todo/.done/audit-design-decisions.md +812 -0
- package/todo/.done/audit-implementation-plan.md +1235 -0
- package/todo/.done/happy-path-polish.md +177 -0
- package/todo/.done/pixelweaver-full-build.md +937 -0
- package/todo/.done/server-multi-frame-design.md +405 -0
- package/todo/.done/typed-dispatcher-design.md +435 -0
- package/todo/.done/unified-toolbar-and-action-system.md +323 -0
- package/todo/.obsolete/comprehensive-audit-obsolete-items.md +33 -0
- package/todo/.obsolete/tauri-desktop-bundle.md +424 -0
- package/todo/comprehensive-audit.md +1085 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +20 -0
- package/uv.lock +1167 -0
- package/vite.config.ts +32 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared drawing utility functions for PixelWeaver's built-in tools.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that compute pixel coordinates for various shapes
|
|
5
|
+
* and primitives. No side effects -- callers apply the results to
|
|
6
|
+
* a PixelBuffer themselves.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { PixelBuffer } from '../../src/lib/canvas/pixel-buffer.js';
|
|
10
|
+
import { hexToRgba } from '../../src/lib/color/color-utils.js';
|
|
11
|
+
|
|
12
|
+
// Re-export the canonical hexToRgba so existing tool/effect plugins keep
|
|
13
|
+
// importing it from drawing-utils without touching their import paths.
|
|
14
|
+
export { hexToRgba };
|
|
15
|
+
|
|
16
|
+
// --- Point type used throughout ---
|
|
17
|
+
|
|
18
|
+
export interface Point {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PixelData {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
r: number;
|
|
27
|
+
g: number;
|
|
28
|
+
b: number;
|
|
29
|
+
a: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- Line algorithms ---
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Standard Bresenham line algorithm.
|
|
36
|
+
* Returns all integer pixel coordinates from (x0,y0) to (x1,y1), inclusive.
|
|
37
|
+
*/
|
|
38
|
+
export function bresenhamLine(x0: number, y0: number, x1: number, y1: number): Point[] {
|
|
39
|
+
const points: Point[] = [];
|
|
40
|
+
|
|
41
|
+
const dx = Math.abs(x1 - x0);
|
|
42
|
+
const dy = Math.abs(y1 - y0);
|
|
43
|
+
const sx = x0 < x1 ? 1 : -1;
|
|
44
|
+
const sy = y0 < y1 ? 1 : -1;
|
|
45
|
+
let err = dx - dy;
|
|
46
|
+
|
|
47
|
+
let cx = x0;
|
|
48
|
+
let cy = y0;
|
|
49
|
+
|
|
50
|
+
for (;;) {
|
|
51
|
+
points.push({ x: cx, y: cy });
|
|
52
|
+
if (cx === x1 && cy === y1) break;
|
|
53
|
+
|
|
54
|
+
const e2 = 2 * err;
|
|
55
|
+
if (e2 > -dy) {
|
|
56
|
+
err -= dy;
|
|
57
|
+
cx += sx;
|
|
58
|
+
}
|
|
59
|
+
if (e2 < dx) {
|
|
60
|
+
err += dx;
|
|
61
|
+
cy += sy;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return points;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* No-doubles Bresenham variant.
|
|
70
|
+
* Like standard Bresenham but never places two consecutive pixels
|
|
71
|
+
* that differ only on the minor axis (no "staircase doubling").
|
|
72
|
+
* This produces a thinner, cleaner line for pixel art.
|
|
73
|
+
*/
|
|
74
|
+
export function bresenhamLineNoDoubles(x0: number, y0: number, x1: number, y1: number): Point[] {
|
|
75
|
+
const points: Point[] = [];
|
|
76
|
+
|
|
77
|
+
const dx = Math.abs(x1 - x0);
|
|
78
|
+
const dy = Math.abs(y1 - y0);
|
|
79
|
+
const sx = x0 < x1 ? 1 : -1;
|
|
80
|
+
const sy = y0 < y1 ? 1 : -1;
|
|
81
|
+
let err = dx - dy;
|
|
82
|
+
|
|
83
|
+
let cx = x0;
|
|
84
|
+
let cy = y0;
|
|
85
|
+
|
|
86
|
+
for (;;) {
|
|
87
|
+
points.push({ x: cx, y: cy });
|
|
88
|
+
if (cx === x1 && cy === y1) break;
|
|
89
|
+
|
|
90
|
+
const e2 = 2 * err;
|
|
91
|
+
// When both axes advance simultaneously, that's a diagonal step -- always keep it.
|
|
92
|
+
// A "double" occurs when only the minor axis advances in isolation.
|
|
93
|
+
// We detect this by checking if both conditions are met; if so, step diagonally.
|
|
94
|
+
if (e2 > -dy && e2 < dx) {
|
|
95
|
+
// Diagonal step -- both axes advance
|
|
96
|
+
err -= dy;
|
|
97
|
+
err += dx;
|
|
98
|
+
cx += sx;
|
|
99
|
+
cy += sy;
|
|
100
|
+
} else if (e2 > -dy) {
|
|
101
|
+
// Major axis step (x advances)
|
|
102
|
+
err -= dy;
|
|
103
|
+
cx += sx;
|
|
104
|
+
} else {
|
|
105
|
+
// Minor axis step (y advances)
|
|
106
|
+
err += dx;
|
|
107
|
+
cy += sy;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return points;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// --- Rectangle algorithms ---
|
|
115
|
+
|
|
116
|
+
/** Returns perimeter points of a rectangle (no duplicates at corners). */
|
|
117
|
+
export function rectOutline(x: number, y: number, w: number, h: number): Point[] {
|
|
118
|
+
if (w <= 0 || h <= 0) return [];
|
|
119
|
+
|
|
120
|
+
const points: Point[] = [];
|
|
121
|
+
const x1 = x + w - 1;
|
|
122
|
+
const y1 = y + h - 1;
|
|
123
|
+
|
|
124
|
+
if (w === 1 && h === 1) {
|
|
125
|
+
points.push({ x, y });
|
|
126
|
+
return points;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Top edge
|
|
130
|
+
for (let px = x; px <= x1; px++) points.push({ x: px, y });
|
|
131
|
+
// Right edge (skip top corner)
|
|
132
|
+
for (let py = y + 1; py <= y1; py++) points.push({ x: x1, y: py });
|
|
133
|
+
// Bottom edge (skip right corner), only if h > 1
|
|
134
|
+
if (h > 1) {
|
|
135
|
+
for (let px = x1 - 1; px >= x; px--) points.push({ x: px, y: y1 });
|
|
136
|
+
}
|
|
137
|
+
// Left edge (skip top and bottom corners), only if w > 1
|
|
138
|
+
if (w > 1) {
|
|
139
|
+
for (let py = y1 - 1; py > y; py--) points.push({ x, y: py });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return points;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Returns all points inside a filled rectangle. */
|
|
146
|
+
export function rectFilled(x: number, y: number, w: number, h: number): Point[] {
|
|
147
|
+
if (w <= 0 || h <= 0) return [];
|
|
148
|
+
|
|
149
|
+
const points: Point[] = [];
|
|
150
|
+
for (let py = y; py < y + h; py++) {
|
|
151
|
+
for (let px = x; px < x + w; px++) {
|
|
152
|
+
points.push({ x: px, y: py });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return points;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// --- Ellipse algorithms ---
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Midpoint ellipse algorithm -- outline only.
|
|
162
|
+
* Returns all points on the perimeter of the ellipse centered at (cx,cy)
|
|
163
|
+
* with radii (rx, ry). Handles degenerate cases (rx=0, ry=0).
|
|
164
|
+
*/
|
|
165
|
+
export function ellipseOutline(cx: number, cy: number, rx: number, ry: number): Point[] {
|
|
166
|
+
if (rx < 0 || ry < 0) return [];
|
|
167
|
+
|
|
168
|
+
// Degenerate: single point
|
|
169
|
+
if (rx === 0 && ry === 0) return [{ x: cx, y: cy }];
|
|
170
|
+
|
|
171
|
+
// Degenerate: vertical line
|
|
172
|
+
if (rx === 0) {
|
|
173
|
+
const points: Point[] = [];
|
|
174
|
+
for (let dy = -ry; dy <= ry; dy++) points.push({ x: cx, y: cy + dy });
|
|
175
|
+
return points;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Degenerate: horizontal line
|
|
179
|
+
if (ry === 0) {
|
|
180
|
+
const points: Point[] = [];
|
|
181
|
+
for (let dx = -rx; dx <= rx; dx++) points.push({ x: cx + dx, y: cy });
|
|
182
|
+
return points;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const pointSet = new Set<string>();
|
|
186
|
+
const points: Point[] = [];
|
|
187
|
+
|
|
188
|
+
function addSymmetric(px: number, py: number): void {
|
|
189
|
+
const coords = [
|
|
190
|
+
{ x: cx + px, y: cy + py },
|
|
191
|
+
{ x: cx - px, y: cy + py },
|
|
192
|
+
{ x: cx + px, y: cy - py },
|
|
193
|
+
{ x: cx - px, y: cy - py },
|
|
194
|
+
];
|
|
195
|
+
for (const p of coords) {
|
|
196
|
+
const key = `${String(p.x)},${String(p.y)}`;
|
|
197
|
+
if (!pointSet.has(key)) {
|
|
198
|
+
pointSet.add(key);
|
|
199
|
+
points.push(p);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Region 1: slope magnitude < 1
|
|
205
|
+
let x = 0;
|
|
206
|
+
let y = ry;
|
|
207
|
+
const rx2 = rx * rx;
|
|
208
|
+
const ry2 = ry * ry;
|
|
209
|
+
let d1 = ry2 - rx2 * ry + 0.25 * rx2;
|
|
210
|
+
|
|
211
|
+
addSymmetric(x, y);
|
|
212
|
+
|
|
213
|
+
while (ry2 * x < rx2 * y) {
|
|
214
|
+
x++;
|
|
215
|
+
if (d1 < 0) {
|
|
216
|
+
d1 += ry2 * (2 * x + 1);
|
|
217
|
+
} else {
|
|
218
|
+
y--;
|
|
219
|
+
d1 += ry2 * (2 * x + 1) - 2 * rx2 * y;
|
|
220
|
+
}
|
|
221
|
+
addSymmetric(x, y);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Region 2: slope magnitude >= 1
|
|
225
|
+
let d2 = ry2 * (x + 0.5) * (x + 0.5) + rx2 * (y - 1) * (y - 1) - rx2 * ry2;
|
|
226
|
+
|
|
227
|
+
while (y >= 0) {
|
|
228
|
+
addSymmetric(x, y);
|
|
229
|
+
y--;
|
|
230
|
+
if (d2 > 0) {
|
|
231
|
+
d2 += rx2 * (1 - 2 * y);
|
|
232
|
+
} else {
|
|
233
|
+
x++;
|
|
234
|
+
d2 += ry2 * (2 * x + 1) + rx2 * (1 - 2 * y);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return points;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Filled ellipse derived from the outline.
|
|
243
|
+
* Computes the outline first, then for each scanline fills between the
|
|
244
|
+
* leftmost and rightmost outline pixels. This ensures the filled version
|
|
245
|
+
* always contains every outline point.
|
|
246
|
+
*/
|
|
247
|
+
export function ellipseFilled(cx: number, cy: number, rx: number, ry: number): Point[] {
|
|
248
|
+
if (rx < 0 || ry < 0) return [];
|
|
249
|
+
if (rx === 0 && ry === 0) return [{ x: cx, y: cy }];
|
|
250
|
+
|
|
251
|
+
// Get outline points and compute min/max x per scanline
|
|
252
|
+
const outline = ellipseOutline(cx, cy, rx, ry);
|
|
253
|
+
const scanlines = new Map<number, { minX: number; maxX: number }>();
|
|
254
|
+
|
|
255
|
+
for (const p of outline) {
|
|
256
|
+
const entry = scanlines.get(p.y);
|
|
257
|
+
if (entry) {
|
|
258
|
+
entry.minX = Math.min(entry.minX, p.x);
|
|
259
|
+
entry.maxX = Math.max(entry.maxX, p.x);
|
|
260
|
+
} else {
|
|
261
|
+
scanlines.set(p.y, { minX: p.x, maxX: p.x });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Fill each scanline between its extents
|
|
266
|
+
const points: Point[] = [];
|
|
267
|
+
for (const [y, { minX, maxX }] of scanlines) {
|
|
268
|
+
for (let x = minX; x <= maxX; x++) {
|
|
269
|
+
points.push({ x, y });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return points;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// --- Diamond algorithms ---
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Diamond (rhombus) outline.
|
|
280
|
+
* The diamond is defined by center (cx, cy) and radii (rx, ry).
|
|
281
|
+
* Its vertices are at (cx +/- rx, cy) and (cx, cy +/- ry).
|
|
282
|
+
* Uses Bresenham lines between the four vertices.
|
|
283
|
+
*/
|
|
284
|
+
export function diamondOutline(cx: number, cy: number, rx: number, ry: number): Point[] {
|
|
285
|
+
if (rx < 0 || ry < 0) return [];
|
|
286
|
+
if (rx === 0 && ry === 0) return [{ x: cx, y: cy }];
|
|
287
|
+
|
|
288
|
+
// Four vertices
|
|
289
|
+
const top: Point = { x: cx, y: cy - ry };
|
|
290
|
+
const right: Point = { x: cx + rx, y: cy };
|
|
291
|
+
const bottom: Point = { x: cx, y: cy + ry };
|
|
292
|
+
const left: Point = { x: cx - rx, y: cy };
|
|
293
|
+
|
|
294
|
+
// Draw four edges, deduplicating shared vertices
|
|
295
|
+
const pointSet = new Set<string>();
|
|
296
|
+
const points: Point[] = [];
|
|
297
|
+
|
|
298
|
+
const edges = [
|
|
299
|
+
[top, right],
|
|
300
|
+
[right, bottom],
|
|
301
|
+
[bottom, left],
|
|
302
|
+
[left, top],
|
|
303
|
+
] as const;
|
|
304
|
+
|
|
305
|
+
for (const [from, to] of edges) {
|
|
306
|
+
const linePoints = bresenhamLine(from.x, from.y, to.x, to.y);
|
|
307
|
+
for (const p of linePoints) {
|
|
308
|
+
const key = `${String(p.x)},${String(p.y)}`;
|
|
309
|
+
if (!pointSet.has(key)) {
|
|
310
|
+
pointSet.add(key);
|
|
311
|
+
points.push(p);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return points;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Filled diamond using scanline approach.
|
|
321
|
+
* For each row from (cy - ry) to (cy + ry), compute the horizontal extent
|
|
322
|
+
* based on linear interpolation of the diamond edges.
|
|
323
|
+
*/
|
|
324
|
+
export function diamondFilled(cx: number, cy: number, rx: number, ry: number): Point[] {
|
|
325
|
+
if (rx < 0 || ry < 0) return [];
|
|
326
|
+
if (rx === 0 && ry === 0) return [{ x: cx, y: cy }];
|
|
327
|
+
|
|
328
|
+
const points: Point[] = [];
|
|
329
|
+
|
|
330
|
+
for (let dy = -ry; dy <= ry; dy++) {
|
|
331
|
+
// Linear interpolation: at distance |dy| from center, the half-width is
|
|
332
|
+
// rx * (1 - |dy| / ry)
|
|
333
|
+
const halfWidth = ry === 0 ? rx : Math.round(rx * (1 - Math.abs(dy) / ry));
|
|
334
|
+
for (let dx = -halfWidth; dx <= halfWidth; dx++) {
|
|
335
|
+
points.push({ x: cx + dx, y: cy + dy });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return points;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// --- Flood fill ---
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Flood fill (BFS) starting from (startX, startY).
|
|
346
|
+
* Returns all connected pixels whose color is within `tolerance` of the
|
|
347
|
+
* starting pixel's color (per-channel max distance).
|
|
348
|
+
* tolerance=0 means exact match (default, preserving legacy behavior).
|
|
349
|
+
* Returns an empty array if the start is out of bounds.
|
|
350
|
+
*/
|
|
351
|
+
export function floodFill(buffer: PixelBuffer, startX: number, startY: number, tolerance = 0): Point[] {
|
|
352
|
+
if (!buffer.inBounds(startX, startY)) return [];
|
|
353
|
+
|
|
354
|
+
const [tr, tg, tb, ta] = buffer.getPixel(startX, startY);
|
|
355
|
+
const visited = new Set<number>();
|
|
356
|
+
const result: Point[] = [];
|
|
357
|
+
const queue: Point[] = [{ x: startX, y: startY }];
|
|
358
|
+
const w = buffer.width;
|
|
359
|
+
|
|
360
|
+
// Color distance: max absolute difference across RGBA channels.
|
|
361
|
+
// When tolerance is 0 this reduces to an exact-match check.
|
|
362
|
+
const matches = (r: number, g: number, b: number, a: number): boolean => {
|
|
363
|
+
if (tolerance === 0) return r === tr && g === tg && b === tb && a === ta;
|
|
364
|
+
return Math.max(Math.abs(r - tr), Math.abs(g - tg), Math.abs(b - tb), Math.abs(a - ta)) <= tolerance;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Encode coords as a single integer for fast Set lookups
|
|
368
|
+
visited.add(startY * w + startX);
|
|
369
|
+
|
|
370
|
+
while (queue.length > 0) {
|
|
371
|
+
const next = queue.shift();
|
|
372
|
+
if (!next) break;
|
|
373
|
+
const { x, y } = next;
|
|
374
|
+
result.push({ x, y });
|
|
375
|
+
|
|
376
|
+
// Check 4-connected neighbors
|
|
377
|
+
const neighbors: Point[] = [
|
|
378
|
+
{ x: x - 1, y },
|
|
379
|
+
{ x: x + 1, y },
|
|
380
|
+
{ x, y: y - 1 },
|
|
381
|
+
{ x, y: y + 1 },
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
for (const n of neighbors) {
|
|
385
|
+
if (!buffer.inBounds(n.x, n.y)) continue;
|
|
386
|
+
const key = n.y * w + n.x;
|
|
387
|
+
if (visited.has(key)) continue;
|
|
388
|
+
visited.add(key);
|
|
389
|
+
|
|
390
|
+
const [r, g, b, a] = buffer.getPixel(n.x, n.y);
|
|
391
|
+
if (matches(r, g, b, a)) {
|
|
392
|
+
queue.push(n);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// --- Pixel snapshot/apply helpers ---
|
|
401
|
+
|
|
402
|
+
/** Read RGBA values at each given point from the buffer. */
|
|
403
|
+
export function snapshotPixels(buffer: PixelBuffer, points: Point[]): PixelData[] {
|
|
404
|
+
return points.map(({ x, y }) => {
|
|
405
|
+
const [r, g, b, a] = buffer.getPixel(x, y);
|
|
406
|
+
return { x, y, r, g, b, a };
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Write RGBA values to the buffer for each given pixel. */
|
|
411
|
+
export function applyPixels(buffer: PixelBuffer, pixels: PixelData[]): void {
|
|
412
|
+
for (const { x, y, r, g, b, a } of pixels) {
|
|
413
|
+
buffer.setPixel(x, y, r, g, b, a);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// --- Undo factory ---
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Factory that returns the standard snapshot-based undo handler.
|
|
421
|
+
* The snapshot (PixelData[]) is provided as the third argument by the
|
|
422
|
+
* dispatcher, which captured it from execute()'s return value.
|
|
423
|
+
*/
|
|
424
|
+
export function makeSnapshotUndo(): (params: Record<string, unknown>, ctx: import('../../src/lib/core/commands.js').CommandContext, snapshot?: unknown) => void {
|
|
425
|
+
return (_params, ctx, snapshot) => {
|
|
426
|
+
const buffer = ctx.getActiveBuffer?.();
|
|
427
|
+
if (!buffer) return;
|
|
428
|
+
applyPixels(buffer, snapshot as PixelData[]);
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blur Effect -- 1px box blur (average of 8 neighbors + self).
|
|
3
|
+
*
|
|
4
|
+
* Registers:
|
|
5
|
+
* - Command: `blur` (tier: 'frame')
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PluginModule } from '../../../src/lib/core/plugin-loader.js';
|
|
9
|
+
import type { PixelBuffer } from '../../../src/lib/canvas/pixel-buffer.js';
|
|
10
|
+
import type { PixelData, Point } from '../drawing-utils.js';
|
|
11
|
+
import { snapshotPixels, applyPixels, makeSnapshotUndo } from '../drawing-utils.js';
|
|
12
|
+
import CircleDot from '~icons/lucide/circle-dot';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Compute a 1px box blur of the buffer.
|
|
16
|
+
* Each pixel becomes the average of itself and its 8 neighbors.
|
|
17
|
+
*/
|
|
18
|
+
export function computeBlur(buffer: PixelBuffer): PixelData[] {
|
|
19
|
+
const w = buffer.width;
|
|
20
|
+
const h = buffer.height;
|
|
21
|
+
const pixels: PixelData[] = [];
|
|
22
|
+
|
|
23
|
+
for (let y = 0; y < h; y++) {
|
|
24
|
+
for (let x = 0; x < w; x++) {
|
|
25
|
+
let totalR = 0, totalG = 0, totalB = 0, totalA = 0;
|
|
26
|
+
let count = 0;
|
|
27
|
+
|
|
28
|
+
// 3x3 neighborhood
|
|
29
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
30
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
31
|
+
const nx = x + dx;
|
|
32
|
+
const ny = y + dy;
|
|
33
|
+
if (buffer.inBounds(nx, ny)) {
|
|
34
|
+
const [r, g, b, a] = buffer.getPixel(nx, ny);
|
|
35
|
+
totalR += r;
|
|
36
|
+
totalG += g;
|
|
37
|
+
totalB += b;
|
|
38
|
+
totalA += a;
|
|
39
|
+
count++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pixels.push({
|
|
45
|
+
x, y,
|
|
46
|
+
r: Math.round(totalR / count),
|
|
47
|
+
g: Math.round(totalG / count),
|
|
48
|
+
b: Math.round(totalB / count),
|
|
49
|
+
a: Math.round(totalA / count),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return pixels;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const blurEffectPlugin: PluginModule = {
|
|
58
|
+
name: 'builtin/effects/blur',
|
|
59
|
+
version: '1.0.0',
|
|
60
|
+
dependencies: [],
|
|
61
|
+
register(api) {
|
|
62
|
+
api.addCommand('blur', {
|
|
63
|
+
tier: 'frame',
|
|
64
|
+
label: 'Blur',
|
|
65
|
+
category: 'Effects',
|
|
66
|
+
icon: CircleDot,
|
|
67
|
+
|
|
68
|
+
execute(_params, ctx) {
|
|
69
|
+
const buffer = ctx.getActiveBuffer?.();
|
|
70
|
+
if (!buffer) return;
|
|
71
|
+
|
|
72
|
+
const allPoints: Point[] = [];
|
|
73
|
+
for (let y = 0; y < buffer.height; y++) {
|
|
74
|
+
for (let x = 0; x < buffer.width; x++) {
|
|
75
|
+
allPoints.push({ x, y });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const snapshot = snapshotPixels(buffer, allPoints);
|
|
79
|
+
|
|
80
|
+
applyPixels(buffer, computeBlur(buffer));
|
|
81
|
+
return snapshot;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
undo: makeSnapshotUndo(),
|
|
85
|
+
|
|
86
|
+
describe() { return 'Applied 1px box blur'; },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
api.addMenuItem('menu:effects:blur', {
|
|
90
|
+
commandId: 'blur',
|
|
91
|
+
menuPath: 'effects',
|
|
92
|
+
group: 'blur-sharpen',
|
|
93
|
+
order: 10,
|
|
94
|
+
label: 'Blur',
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
};
|