@prismer/runtime 2.2.55 → 2.2.63
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/CHANGELOG.md +574 -0
- package/apc/skills/releasing-test/SKILL.md +9 -0
- package/built-in-skills/agent-coordination/SKILL.md +21 -4
- package/built-in-skills/agent-meta/SKILL.md +11 -0
- package/built-in-skills/assets/SKILL.md +1 -1
- package/built-in-skills/canvas-design/SKILL.md +3 -3
- package/built-in-skills/canvas-design/canvas-fonts/IBMPlexSerif-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/InstrumentSerif-OFL.txt +93 -0
- package/built-in-skills/canvas-design/canvas-fonts/LICENSE-SOURCES.md +16 -0
- package/built-in-skills/claim-agent-ownership/SKILL.md +7 -3
- package/built-in-skills/claude-api/SKILL.md +1 -1
- package/built-in-skills/claude-api/python/claude-api/README.md +2 -2
- package/built-in-skills/claude-api/python/claude-api/tool-use.md +9 -5
- package/built-in-skills/claude-api/python/managed-agents/README.md +11 -1
- package/built-in-skills/claude-api/shared/error-codes.md +4 -4
- package/built-in-skills/claude-api/shared/managed-agents-client-patterns.md +4 -2
- package/built-in-skills/claude-api/shared/managed-agents-core.md +1 -2
- package/built-in-skills/claude-api/shared/managed-agents-events.md +41 -22
- package/built-in-skills/claude-api/shared/managed-agents-multiagent.md +5 -3
- package/built-in-skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +1 -1
- package/built-in-skills/claude-api/shared/managed-agents-tools.md +2 -3
- package/built-in-skills/claude-api/shared/model-migration.md +3 -3
- package/built-in-skills/claude-api/shared/models.md +1 -1
- package/built-in-skills/claude-api/typescript/claude-api/batches.md +1 -1
- package/built-in-skills/claude-api/typescript/claude-api/files-api.md +3 -1
- package/built-in-skills/claude-api/typescript/claude-api/tool-use.md +8 -2
- package/built-in-skills/claude-api/typescript/managed-agents/README.md +8 -2
- package/built-in-skills/codebase-design/SKILL.md +15 -1
- package/built-in-skills/codebase-design/references/requesting-code-review/GUIDE.md +291 -0
- package/built-in-skills/codebase-design/references/requesting-code-review/LICENSE +21 -0
- package/built-in-skills/codebase-design/references/requesting-code-review/LICENSE.superpowers +21 -0
- package/built-in-skills/codebase-design/references/requesting-code-review/NOTICE.md +37 -0
- package/built-in-skills/codebase-design/references/sdlc-review/GUIDE.md +130 -0
- package/built-in-skills/codebase-design/references/sdlc-review/LICENSE +21 -0
- package/built-in-skills/codebase-design/references/sdlc-review/NOTICE.md +17 -0
- package/built-in-skills/codebase-design/references/simplify-code/GUIDE.md +269 -0
- package/built-in-skills/codebase-design/references/simplify-code/LICENSE +21 -0
- package/built-in-skills/codebase-design/references/simplify-code/NOTICE.md +37 -0
- package/built-in-skills/competitor-intelligence/LICENSE +21 -0
- package/built-in-skills/competitor-intelligence/NOTICE.md +6 -0
- package/built-in-skills/competitor-intelligence/SKILL.md +125 -0
- package/built-in-skills/competitor-intelligence/scripts/watch_state.py +160 -0
- package/built-in-skills/conversation-compaction/SKILL.md +3 -0
- package/built-in-skills/council-creator/SKILL.md +4 -3
- package/built-in-skills/diagnosing-bugs/SKILL.md +12 -0
- package/built-in-skills/diagnosing-bugs/references/desktop-dom/GUIDE.md +64 -0
- package/built-in-skills/diagnosing-bugs/references/desktop-dom/LICENSE +21 -0
- package/built-in-skills/diagnosing-bugs/references/desktop-dom/NOTICE.md +17 -0
- package/built-in-skills/diagnosing-bugs/references/systematic-debugging/GUIDE.md +410 -0
- package/built-in-skills/diagnosing-bugs/references/systematic-debugging/LICENSE +21 -0
- package/built-in-skills/diagnosing-bugs/references/systematic-debugging/LICENSE.superpowers +21 -0
- package/built-in-skills/diagnosing-bugs/references/systematic-debugging/NOTICE.md +37 -0
- package/built-in-skills/doc-coauthoring/SKILL.md +6 -0
- package/built-in-skills/doc-coauthoring/references/humanizer/GUIDE.md +647 -0
- package/built-in-skills/doc-coauthoring/references/humanizer/LICENSE +21 -0
- package/built-in-skills/doc-coauthoring/references/humanizer/LICENSE.hermes +21 -0
- package/built-in-skills/doc-coauthoring/references/humanizer/PROVENANCE.md +7 -0
- package/built-in-skills/document-actions/LICENSE +21 -0
- package/built-in-skills/document-actions/NOTICE.md +21 -0
- package/built-in-skills/document-actions/SKILL.md +46 -0
- package/built-in-skills/evidence-citations/LICENSE +21 -0
- package/built-in-skills/evidence-citations/NOTICE.md +21 -0
- package/built-in-skills/evidence-citations/SKILL.md +46 -0
- package/built-in-skills/evidence-citations/references/citation-formats.md +68 -0
- package/built-in-skills/evidence-citations/references/grounding-rationale.md +64 -0
- package/built-in-skills/evidence-citations/scripts/sources.py +720 -0
- package/built-in-skills/frontend-design/SKILL.md +6 -0
- package/built-in-skills/frontend-design/references/claude-design/GUIDE.md +650 -0
- package/built-in-skills/frontend-design/references/claude-design/LICENSE +21 -0
- package/built-in-skills/frontend-design/references/claude-design/LICENSE.hermes +21 -0
- package/built-in-skills/frontend-design/references/claude-design/PROVENANCE.md +7 -0
- package/built-in-skills/image-generate/SKILL.md +5 -0
- package/built-in-skills/image-generate/scripts/generate-and-deliver.mjs +24 -3
- package/built-in-skills/mcp-builder/reference/evaluation.md +11 -2
- package/built-in-skills/mcp-builder/reference/node_mcp_server.md +34 -5
- package/built-in-skills/mcp-builder/reference/python_mcp_server.md +6 -2
- package/built-in-skills/mcp-builder/scripts/connections.py +1 -1
- package/built-in-skills/mcp-builder/scripts/evaluation.py +52 -34
- package/built-in-skills/mcp-builder/scripts/requirements.txt +2 -2
- package/built-in-skills/meeting-actions/LICENSE +21 -0
- package/built-in-skills/meeting-actions/NOTICE.md +21 -0
- package/built-in-skills/meeting-actions/SKILL.md +48 -0
- package/built-in-skills/memory/SKILL.md +27 -3
- package/built-in-skills/memory/references/llm-wiki/GUIDE.md +468 -0
- package/built-in-skills/memory/references/llm-wiki/LICENSE +21 -0
- package/built-in-skills/memory/references/llm-wiki/NOTICE.md +4 -0
- package/built-in-skills/memory/references/llm-wiki/scripts/lint_wiki.py +84 -0
- package/built-in-skills/memory/references/llm-wiki/scripts/requirements.txt +1 -0
- package/built-in-skills/memory/references/llm-wiki/templates/SCHEMA.md +19 -0
- package/built-in-skills/memory/references/llm-wiki/templates/index.md +9 -0
- package/built-in-skills/memory/references/llm-wiki/templates/log.md +4 -0
- package/built-in-skills/memory-dream/SKILL.md +8 -15
- package/built-in-skills/memory-import/SKILL.md +51 -0
- package/built-in-skills/office-artifacts/NOTICE.md +15 -0
- package/built-in-skills/office-artifacts/SKILL.md +20 -0
- package/built-in-skills/office-artifacts/references/RESOURCE-LIMITS.md +44 -0
- package/built-in-skills/office-artifacts/references/docx/GUIDE.md +196 -0
- package/built-in-skills/office-artifacts/references/docx/LICENSE +21 -0
- package/built-in-skills/office-artifacts/references/docx/NOTICE.md +21 -0
- package/built-in-skills/office-artifacts/references/docx/references/revisions-and-comments.md +88 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_comments.py +292 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_common.py +93 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_create.py +177 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_edit.py +250 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_read.py +169 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_revisions.py +149 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_template.py +70 -0
- package/built-in-skills/office-artifacts/references/docx/scripts/docx_validate.py +155 -0
- package/built-in-skills/office-artifacts/references/docx/tests/test_docx_skill.py +525 -0
- package/built-in-skills/office-artifacts/references/pdf/GUIDE.md +125 -0
- package/built-in-skills/office-artifacts/references/pdf/LICENSE +21 -0
- package/built-in-skills/office-artifacts/references/pdf/NOTICE.md +21 -0
- package/built-in-skills/office-artifacts/references/pdf/references/forms.md +99 -0
- package/built-in-skills/office-artifacts/references/pdf/references/nano-pdf-editing.md +40 -0
- package/built-in-skills/office-artifacts/references/pdf/references/ocr-extraction.md +165 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/_raster.py +87 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/extract_marker.py +95 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/extract_pymupdf.py +107 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_create.py +153 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_fill_form.py +97 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_form_layout.py +168 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_make_form.py +145 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_merge.py +50 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_meta.py +123 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_page_image.py +101 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_read.py +153 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_secure.py +71 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_split.py +86 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_stamp.py +145 -0
- package/built-in-skills/office-artifacts/references/pdf/scripts/pdf_watermark.py +51 -0
- package/built-in-skills/office-artifacts/references/pdf/tests/test_pdf_skill.py +414 -0
- package/built-in-skills/office-artifacts/references/powerpoint/GUIDE.md +220 -0
- package/built-in-skills/office-artifacts/references/powerpoint/LICENSE +21 -0
- package/built-in-skills/office-artifacts/references/powerpoint/NOTICE.md +21 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_create.py +214 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_edit.py +442 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_from_template.py +88 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_read.py +131 -0
- package/built-in-skills/office-artifacts/references/powerpoint/scripts/pptx_render.py +104 -0
- package/built-in-skills/office-artifacts/references/powerpoint/tests/test_powerpoint_skill.py +478 -0
- package/built-in-skills/office-artifacts/references/xlsx/GUIDE.md +196 -0
- package/built-in-skills/office-artifacts/references/xlsx/LICENSE +21 -0
- package/built-in-skills/office-artifacts/references/xlsx/NOTICE.md +21 -0
- package/built-in-skills/office-artifacts/references/xlsx/references/restructuring.md +74 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/csv_to_xlsx.py +108 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_create.py +259 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_edit.py +269 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_read.py +160 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_recalc.py +111 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_restructure.py +418 -0
- package/built-in-skills/office-artifacts/references/xlsx/scripts/xlsx_to_csv.py +69 -0
- package/built-in-skills/office-artifacts/references/xlsx/tests/test_xlsx_skill.py +542 -0
- package/built-in-skills/office-artifacts/tests/test_resource_regressions.py +272 -0
- package/built-in-skills/persona-generator/SKILL.md +9 -9
- package/built-in-skills/pkf-svg/SKILL.md +5 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/GUIDE.md +148 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/LICENSE +21 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/LICENSE.hermes +21 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/PROVENANCE.md +7 -0
- package/built-in-skills/pkf-svg/references/architecture-diagram/templates/template.html +345 -0
- package/built-in-skills/pkf-writing/SKILL.md +36 -14
- package/built-in-skills/prismer-airtable/LICENSE +21 -0
- package/built-in-skills/prismer-airtable/NOTICE.md +11 -0
- package/built-in-skills/prismer-airtable/SKILL.md +248 -0
- package/built-in-skills/prismer-airtable/scripts/airtable_client.py +76 -0
- package/built-in-skills/prismer-apple-notes/LICENSE +21 -0
- package/built-in-skills/prismer-apple-notes/NOTICE.md +17 -0
- package/built-in-skills/prismer-apple-notes/SKILL.md +118 -0
- package/built-in-skills/prismer-apple-notes/scripts/readiness.py +47 -0
- package/built-in-skills/prismer-apple-reminders/LICENSE +21 -0
- package/built-in-skills/prismer-apple-reminders/NOTICE.md +17 -0
- package/built-in-skills/prismer-apple-reminders/SKILL.md +155 -0
- package/built-in-skills/prismer-apple-reminders/scripts/readiness.py +47 -0
- package/built-in-skills/prismer-ascii-video/LICENSE +21 -0
- package/built-in-skills/prismer-ascii-video/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-ascii-video/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-ascii-video/README.md +290 -0
- package/built-in-skills/prismer-ascii-video/SKILL.md +252 -0
- package/built-in-skills/prismer-ascii-video/provenance/category-DESCRIPTION.md +3 -0
- package/built-in-skills/prismer-ascii-video/references/architecture.md +807 -0
- package/built-in-skills/prismer-ascii-video/references/composition.md +900 -0
- package/built-in-skills/prismer-ascii-video/references/effects.md +1877 -0
- package/built-in-skills/prismer-ascii-video/references/inputs.md +693 -0
- package/built-in-skills/prismer-ascii-video/references/optimization.md +688 -0
- package/built-in-skills/prismer-ascii-video/references/scenes.md +1018 -0
- package/built-in-skills/prismer-ascii-video/references/shaders.md +1408 -0
- package/built-in-skills/prismer-ascii-video/references/troubleshooting.md +360 -0
- package/built-in-skills/prismer-baoyu-infographic/LICENSE +21 -0
- package/built-in-skills/prismer-baoyu-infographic/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-baoyu-infographic/LICENSE.origin +21 -0
- package/built-in-skills/prismer-baoyu-infographic/PORT_NOTES.md +37 -0
- package/built-in-skills/prismer-baoyu-infographic/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-baoyu-infographic/SKILL.md +240 -0
- package/built-in-skills/prismer-baoyu-infographic/references/analysis-framework.md +182 -0
- package/built-in-skills/prismer-baoyu-infographic/references/base-prompt.md +43 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/bento-grid.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/binary-comparison.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/bridge.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/circular-flow.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/comic-strip.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/comparison-matrix.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/dashboard.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/dense-modules.md +72 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/funnel.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/hierarchical-layers.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/hub-spoke.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/iceberg.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/isometric-map.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/jigsaw.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/linear-progression.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/periodic-table.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/story-mountain.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/structural-breakdown.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/tree-branching.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/venn-diagram.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/layouts/winding-roadmap.md +41 -0
- package/built-in-skills/prismer-baoyu-infographic/references/structured-content-template.md +244 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/aged-academia.md +36 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/bold-graphic.md +36 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/chalkboard.md +61 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/claymation.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/corporate-memphis.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/craft-handmade.md +44 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/cyberpunk-neon.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/hand-drawn-edu.md +63 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/ikea-manual.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/kawaii.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/knolling.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/lego-brick.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/morandi-journal.md +60 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/origami.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/pixel-art.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/pop-laboratory.md +48 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/retro-pop-grid.md +47 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/storybook-watercolor.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/subway-map.md +29 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/technical-schematic.md +36 -0
- package/built-in-skills/prismer-baoyu-infographic/references/styles/ui-wireframe.md +29 -0
- package/built-in-skills/prismer-blocked-page-recovery/LICENSE +21 -0
- package/built-in-skills/prismer-blocked-page-recovery/NOTICE.md +17 -0
- package/built-in-skills/prismer-blocked-page-recovery/SKILL.md +158 -0
- package/built-in-skills/prismer-blocked-page-recovery/scripts/recover_page.py +311 -0
- package/built-in-skills/prismer-box/LICENSE +21 -0
- package/built-in-skills/prismer-box/NOTICE.md +11 -0
- package/built-in-skills/prismer-box/SKILL.md +147 -0
- package/built-in-skills/prismer-box/references/bulk-operations.md +43 -0
- package/built-in-skills/prismer-box/references/cli-guide.md +133 -0
- package/built-in-skills/prismer-box/references/content-workflows.md +86 -0
- package/built-in-skills/prismer-box/references/hubs.md +77 -0
- package/built-in-skills/prismer-box/references/oauth-setup.md +92 -0
- package/built-in-skills/prismer-box/references/rest-api.md +37 -0
- package/built-in-skills/prismer-box/references/sdk-development.md +85 -0
- package/built-in-skills/prismer-box/references/search-and-ai.md +147 -0
- package/built-in-skills/prismer-box/references/troubleshooting.md +33 -0
- package/built-in-skills/prismer-box/references/webhooks-and-events.md +43 -0
- package/built-in-skills/prismer-codebase-inspection/LICENSE +21 -0
- package/built-in-skills/prismer-codebase-inspection/NOTICE.md +17 -0
- package/built-in-skills/prismer-codebase-inspection/SKILL.md +141 -0
- package/built-in-skills/prismer-computer-use/LICENSE +21 -0
- package/built-in-skills/prismer-computer-use/NOTICE.md +17 -0
- package/built-in-skills/prismer-computer-use/SKILL.md +372 -0
- package/built-in-skills/prismer-design-md/LICENSE +21 -0
- package/built-in-skills/prismer-design-md/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-design-md/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-design-md/SKILL.md +227 -0
- package/built-in-skills/prismer-design-md/templates/starter.md +99 -0
- package/built-in-skills/prismer-email-inbox-triage/LICENSE +21 -0
- package/built-in-skills/prismer-email-inbox-triage/NOTICE.md +11 -0
- package/built-in-skills/prismer-email-inbox-triage/SKILL.md +116 -0
- package/built-in-skills/prismer-findmy/LICENSE +21 -0
- package/built-in-skills/prismer-findmy/NOTICE.md +17 -0
- package/built-in-skills/prismer-findmy/SKILL.md +150 -0
- package/built-in-skills/prismer-findmy/scripts/readiness.py +47 -0
- package/built-in-skills/prismer-gif-search/LICENSE +21 -0
- package/built-in-skills/prismer-gif-search/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-gif-search/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-gif-search/SKILL.md +97 -0
- package/built-in-skills/prismer-gif-search/scripts/tenor.py +81 -0
- package/built-in-skills/prismer-github/LICENSE +21 -0
- package/built-in-skills/prismer-github/NOTICE.md +17 -0
- package/built-in-skills/prismer-github/SKILL.md +74 -0
- package/built-in-skills/prismer-github/references/auth.md +91 -0
- package/built-in-skills/prismer-github/references/ci-troubleshooting.md +193 -0
- package/built-in-skills/prismer-github/references/code-review.md +465 -0
- package/built-in-skills/prismer-github/references/conventional-commits.md +81 -0
- package/built-in-skills/prismer-github/references/github-api-cheatsheet.md +177 -0
- package/built-in-skills/prismer-github/references/issue-to-pr.md +74 -0
- package/built-in-skills/prismer-github/references/issues.md +336 -0
- package/built-in-skills/prismer-github/references/pr-workflow.md +346 -0
- package/built-in-skills/prismer-github/references/repo-management.md +484 -0
- package/built-in-skills/prismer-github/references/review-output-template.md +84 -0
- package/built-in-skills/prismer-github/scripts/ensure-gh.py +132 -0
- package/built-in-skills/prismer-github/scripts/gh-env.sh +25 -0
- package/built-in-skills/prismer-github/scripts/git-credential-token.py +68 -0
- package/built-in-skills/prismer-github/scripts/github-api.py +71 -0
- package/built-in-skills/prismer-github/scripts/github-context.py +28 -0
- package/built-in-skills/prismer-github/templates/bug-report.md +35 -0
- package/built-in-skills/prismer-github/templates/feature-request.md +31 -0
- package/built-in-skills/prismer-github/templates/pr-body-bugfix.md +35 -0
- package/built-in-skills/prismer-github/templates/pr-body-feature.md +33 -0
- package/built-in-skills/prismer-google-workspace/LICENSE +21 -0
- package/built-in-skills/prismer-google-workspace/NOTICE.md +11 -0
- package/built-in-skills/prismer-google-workspace/SKILL.md +369 -0
- package/built-in-skills/prismer-google-workspace/references/daily-brief.md +65 -0
- package/built-in-skills/prismer-google-workspace/references/gmail-search-syntax.md +63 -0
- package/built-in-skills/prismer-google-workspace/scripts/_hermes_home.py +17 -0
- package/built-in-skills/prismer-google-workspace/scripts/_private_state.py +39 -0
- package/built-in-skills/prismer-google-workspace/scripts/google_api.py +1339 -0
- package/built-in-skills/prismer-google-workspace/scripts/gws_bridge.py +121 -0
- package/built-in-skills/prismer-google-workspace/scripts/setup.py +563 -0
- package/built-in-skills/prismer-google-workspace/tests/test_productivity_regressions.py +214 -0
- package/built-in-skills/prismer-hermes-agent/LICENSE +21 -0
- package/built-in-skills/prismer-hermes-agent/NOTICE.md +17 -0
- package/built-in-skills/prismer-hermes-agent/SKILL.md +247 -0
- package/built-in-skills/prismer-hermes-agent/references/background-systems.md +108 -0
- package/built-in-skills/prismer-hermes-agent/references/cli-reference.md +158 -0
- package/built-in-skills/prismer-hermes-agent/references/configuration.md +96 -0
- package/built-in-skills/prismer-hermes-agent/references/contributor-guide.md +151 -0
- package/built-in-skills/prismer-hermes-agent/references/delegate-task-concurrency-diagnosis.md +47 -0
- package/built-in-skills/prismer-hermes-agent/references/desktop-plugins.md +215 -0
- package/built-in-skills/prismer-hermes-agent/references/native-mcp.md +351 -0
- package/built-in-skills/prismer-hermes-agent/references/petdex.md +82 -0
- package/built-in-skills/prismer-hermes-agent/references/portal-auth-for-third-party-apps.md +135 -0
- package/built-in-skills/prismer-hermes-agent/references/project-context-files.md +55 -0
- package/built-in-skills/prismer-hermes-agent/references/providers-and-models.md +81 -0
- package/built-in-skills/prismer-hermes-agent/references/security-privacy.md +73 -0
- package/built-in-skills/prismer-hermes-agent/references/slash-commands.md +119 -0
- package/built-in-skills/prismer-hermes-agent/references/themes.md +134 -0
- package/built-in-skills/prismer-hermes-agent/references/troubleshooting.md +87 -0
- package/built-in-skills/prismer-hermes-agent/references/tui-widgets.md +144 -0
- package/built-in-skills/prismer-hermes-agent/references/webhooks.md +219 -0
- package/built-in-skills/prismer-hermes-agent/references/windows-quirks.md +64 -0
- package/built-in-skills/prismer-hermes-agent/templates/clock.mjs +51 -0
- package/built-in-skills/prismer-hermes-agent/templates/plugin.js +97 -0
- package/built-in-skills/prismer-hermes-agent/templates/skin.yaml +49 -0
- package/built-in-skills/prismer-himalaya/LICENSE +21 -0
- package/built-in-skills/prismer-himalaya/NOTICE.md +11 -0
- package/built-in-skills/prismer-himalaya/SKILL.md +340 -0
- package/built-in-skills/prismer-himalaya/references/configuration.md +227 -0
- package/built-in-skills/prismer-himalaya/references/message-composition.md +206 -0
- package/built-in-skills/prismer-himalaya/scripts/compose_draft.py +48 -0
- package/built-in-skills/prismer-im-collab/SKILL.md +1 -1
- package/built-in-skills/prismer-imessage/LICENSE +21 -0
- package/built-in-skills/prismer-imessage/NOTICE.md +17 -0
- package/built-in-skills/prismer-imessage/SKILL.md +128 -0
- package/built-in-skills/prismer-imessage/scripts/readiness.py +47 -0
- package/built-in-skills/prismer-manim-video/LICENSE +21 -0
- package/built-in-skills/prismer-manim-video/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-manim-video/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-manim-video/README.md +23 -0
- package/built-in-skills/prismer-manim-video/SKILL.md +279 -0
- package/built-in-skills/prismer-manim-video/references/animation-design-thinking.md +161 -0
- package/built-in-skills/prismer-manim-video/references/animations.md +282 -0
- package/built-in-skills/prismer-manim-video/references/camera-and-3d.md +135 -0
- package/built-in-skills/prismer-manim-video/references/decorations.md +202 -0
- package/built-in-skills/prismer-manim-video/references/equations.md +216 -0
- package/built-in-skills/prismer-manim-video/references/graphs-and-data.md +163 -0
- package/built-in-skills/prismer-manim-video/references/mobjects.md +333 -0
- package/built-in-skills/prismer-manim-video/references/paper-explainer.md +255 -0
- package/built-in-skills/prismer-manim-video/references/production-quality.md +190 -0
- package/built-in-skills/prismer-manim-video/references/rendering.md +187 -0
- package/built-in-skills/prismer-manim-video/references/scene-planning.md +118 -0
- package/built-in-skills/prismer-manim-video/references/troubleshooting.md +135 -0
- package/built-in-skills/prismer-manim-video/references/updaters-and-trackers.md +260 -0
- package/built-in-skills/prismer-manim-video/references/visual-design.md +124 -0
- package/built-in-skills/prismer-manim-video/scripts/setup.sh +15 -0
- package/built-in-skills/prismer-maps/LICENSE +21 -0
- package/built-in-skills/prismer-maps/NOTICE.md +11 -0
- package/built-in-skills/prismer-maps/SKILL.md +240 -0
- package/built-in-skills/prismer-maps/scripts/maps_client.py +1324 -0
- package/built-in-skills/prismer-node-inspect-debugger/LICENSE +21 -0
- package/built-in-skills/prismer-node-inspect-debugger/NOTICE.md +17 -0
- package/built-in-skills/prismer-node-inspect-debugger/SKILL.md +347 -0
- package/built-in-skills/prismer-notion/LICENSE +21 -0
- package/built-in-skills/prismer-notion/NOTICE.md +11 -0
- package/built-in-skills/prismer-notion/SKILL.md +495 -0
- package/built-in-skills/prismer-notion/references/block-types.md +112 -0
- package/built-in-skills/prismer-obsidian/LICENSE +21 -0
- package/built-in-skills/prismer-obsidian/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-obsidian/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-obsidian/SKILL.md +80 -0
- package/built-in-skills/prismer-obsidian/provenance/category-DESCRIPTION.md +3 -0
- package/built-in-skills/prismer-obsidian/scripts/write_note.py +84 -0
- package/built-in-skills/prismer-p5js/LICENSE +21 -0
- package/built-in-skills/prismer-p5js/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-p5js/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-p5js/README.md +64 -0
- package/built-in-skills/prismer-p5js/SKILL.md +574 -0
- package/built-in-skills/prismer-p5js/references/animation.md +439 -0
- package/built-in-skills/prismer-p5js/references/color-systems.md +355 -0
- package/built-in-skills/prismer-p5js/references/core-api.md +410 -0
- package/built-in-skills/prismer-p5js/references/export-pipeline.md +534 -0
- package/built-in-skills/prismer-p5js/references/interaction.md +398 -0
- package/built-in-skills/prismer-p5js/references/shapes-and-geometry.md +300 -0
- package/built-in-skills/prismer-p5js/references/troubleshooting.md +532 -0
- package/built-in-skills/prismer-p5js/references/typography.md +302 -0
- package/built-in-skills/prismer-p5js/references/visual-effects.md +905 -0
- package/built-in-skills/prismer-p5js/references/webgl-and-3d.md +423 -0
- package/built-in-skills/prismer-p5js/scripts/export-frames.js +91 -0
- package/built-in-skills/prismer-p5js/scripts/render.sh +113 -0
- package/built-in-skills/prismer-p5js/scripts/serve.sh +25 -0
- package/built-in-skills/prismer-p5js/scripts/setup.sh +87 -0
- package/built-in-skills/prismer-p5js/templates/viewer.html +401 -0
- package/built-in-skills/prismer-popular-web-designs/LICENSE +21 -0
- package/built-in-skills/prismer-popular-web-designs/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-popular-web-designs/LICENSE.origin +21 -0
- package/built-in-skills/prismer-popular-web-designs/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-popular-web-designs/SKILL.md +218 -0
- package/built-in-skills/prismer-popular-web-designs/templates/airbnb.md +259 -0
- package/built-in-skills/prismer-popular-web-designs/templates/airtable.md +102 -0
- package/built-in-skills/prismer-popular-web-designs/templates/apple.md +326 -0
- package/built-in-skills/prismer-popular-web-designs/templates/bmw.md +193 -0
- package/built-in-skills/prismer-popular-web-designs/templates/cal.md +272 -0
- package/built-in-skills/prismer-popular-web-designs/templates/claude.md +325 -0
- package/built-in-skills/prismer-popular-web-designs/templates/clay.md +317 -0
- package/built-in-skills/prismer-popular-web-designs/templates/clickhouse.md +294 -0
- package/built-in-skills/prismer-popular-web-designs/templates/cohere.md +279 -0
- package/built-in-skills/prismer-popular-web-designs/templates/coinbase.md +142 -0
- package/built-in-skills/prismer-popular-web-designs/templates/composio.md +320 -0
- package/built-in-skills/prismer-popular-web-designs/templates/cursor.md +322 -0
- package/built-in-skills/prismer-popular-web-designs/templates/elevenlabs.md +278 -0
- package/built-in-skills/prismer-popular-web-designs/templates/expo.md +294 -0
- package/built-in-skills/prismer-popular-web-designs/templates/figma.md +233 -0
- package/built-in-skills/prismer-popular-web-designs/templates/framer.md +259 -0
- package/built-in-skills/prismer-popular-web-designs/templates/hashicorp.md +291 -0
- package/built-in-skills/prismer-popular-web-designs/templates/ibm.md +345 -0
- package/built-in-skills/prismer-popular-web-designs/templates/intercom.md +159 -0
- package/built-in-skills/prismer-popular-web-designs/templates/kraken.md +138 -0
- package/built-in-skills/prismer-popular-web-designs/templates/linear.app.md +380 -0
- package/built-in-skills/prismer-popular-web-designs/templates/lovable.md +311 -0
- package/built-in-skills/prismer-popular-web-designs/templates/minimax.md +270 -0
- package/built-in-skills/prismer-popular-web-designs/templates/mintlify.md +339 -0
- package/built-in-skills/prismer-popular-web-designs/templates/miro.md +121 -0
- package/built-in-skills/prismer-popular-web-designs/templates/mistral.ai.md +274 -0
- package/built-in-skills/prismer-popular-web-designs/templates/mongodb.md +279 -0
- package/built-in-skills/prismer-popular-web-designs/templates/notion.md +322 -0
- package/built-in-skills/prismer-popular-web-designs/templates/nvidia.md +306 -0
- package/built-in-skills/prismer-popular-web-designs/templates/ollama.md +280 -0
- package/built-in-skills/prismer-popular-web-designs/templates/opencode.ai.md +294 -0
- package/built-in-skills/prismer-popular-web-designs/templates/pinterest.md +243 -0
- package/built-in-skills/prismer-popular-web-designs/templates/posthog.md +269 -0
- package/built-in-skills/prismer-popular-web-designs/templates/raycast.md +281 -0
- package/built-in-skills/prismer-popular-web-designs/templates/replicate.md +274 -0
- package/built-in-skills/prismer-popular-web-designs/templates/resend.md +316 -0
- package/built-in-skills/prismer-popular-web-designs/templates/revolut.md +198 -0
- package/built-in-skills/prismer-popular-web-designs/templates/runwayml.md +257 -0
- package/built-in-skills/prismer-popular-web-designs/templates/sanity.md +370 -0
- package/built-in-skills/prismer-popular-web-designs/templates/sentry.md +275 -0
- package/built-in-skills/prismer-popular-web-designs/templates/spacex.md +207 -0
- package/built-in-skills/prismer-popular-web-designs/templates/spotify.md +259 -0
- package/built-in-skills/prismer-popular-web-designs/templates/stripe.md +335 -0
- package/built-in-skills/prismer-popular-web-designs/templates/supabase.md +268 -0
- package/built-in-skills/prismer-popular-web-designs/templates/superhuman.md +265 -0
- package/built-in-skills/prismer-popular-web-designs/templates/together.ai.md +276 -0
- package/built-in-skills/prismer-popular-web-designs/templates/uber.md +308 -0
- package/built-in-skills/prismer-popular-web-designs/templates/vercel.md +323 -0
- package/built-in-skills/prismer-popular-web-designs/templates/voltagent.md +336 -0
- package/built-in-skills/prismer-popular-web-designs/templates/warp.md +266 -0
- package/built-in-skills/prismer-popular-web-designs/templates/webflow.md +105 -0
- package/built-in-skills/prismer-popular-web-designs/templates/wise.md +186 -0
- package/built-in-skills/prismer-popular-web-designs/templates/x.ai.md +270 -0
- package/built-in-skills/prismer-popular-web-designs/templates/zapier.md +341 -0
- package/built-in-skills/prismer-product-price-monitor/LICENSE +21 -0
- package/built-in-skills/prismer-product-price-monitor/NOTICE.md +11 -0
- package/built-in-skills/prismer-product-price-monitor/SKILL.md +97 -0
- package/built-in-skills/prismer-product-price-monitor/scripts/watch_state.py +116 -0
- package/built-in-skills/prismer-python-debugpy/LICENSE +21 -0
- package/built-in-skills/prismer-python-debugpy/NOTICE.md +17 -0
- package/built-in-skills/prismer-python-debugpy/SKILL.md +377 -0
- package/built-in-skills/prismer-python-debugpy/scripts/dap_client.py +68 -0
- package/built-in-skills/prismer-songsee/LICENSE +21 -0
- package/built-in-skills/prismer-songsee/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-songsee/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-songsee/SKILL.md +91 -0
- package/built-in-skills/prismer-songwriting-and-ai-music/LICENSE +21 -0
- package/built-in-skills/prismer-songwriting-and-ai-music/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-songwriting-and-ai-music/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-songwriting-and-ai-music/SKILL.md +317 -0
- package/built-in-skills/prismer-spike/LICENSE +21 -0
- package/built-in-skills/prismer-spike/LICENSE.gsd +21 -0
- package/built-in-skills/prismer-spike/NOTICE.md +17 -0
- package/built-in-skills/prismer-spike/SKILL.md +212 -0
- package/built-in-skills/prismer-teams-meeting-pipeline/LICENSE +21 -0
- package/built-in-skills/prismer-teams-meeting-pipeline/NOTICE.md +11 -0
- package/built-in-skills/prismer-teams-meeting-pipeline/SKILL.md +156 -0
- package/built-in-skills/prismer-xurl/LICENSE +21 -0
- package/built-in-skills/prismer-xurl/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-xurl/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-xurl/SKILL.md +444 -0
- package/built-in-skills/prismer-xurl/provenance/category-DESCRIPTION.md +3 -0
- package/built-in-skills/prismer-youtube-content/LICENSE +21 -0
- package/built-in-skills/prismer-youtube-content/LICENSE.hermes +21 -0
- package/built-in-skills/prismer-youtube-content/PROVENANCE.md +7 -0
- package/built-in-skills/prismer-youtube-content/SKILL.md +86 -0
- package/built-in-skills/prismer-youtube-content/provenance/category-DESCRIPTION.md +3 -0
- package/built-in-skills/prismer-youtube-content/references/output-formats.md +56 -0
- package/built-in-skills/prismer-youtube-content/scripts/fetch_transcript.py +146 -0
- package/built-in-skills/research-arxiv/LICENSE +21 -0
- package/built-in-skills/research-arxiv/NOTICE.md +4 -0
- package/built-in-skills/research-arxiv/SKILL.md +263 -0
- package/built-in-skills/research-arxiv/scripts/search_arxiv.py +111 -0
- package/built-in-skills/role-builder/SKILL.md +7 -1
- package/built-in-skills/role-builder/scripts/author-role.mjs +23 -7
- package/built-in-skills/role-builder/scripts/ingest-role.mjs +15 -117
- package/built-in-skills/role-builder/scripts/instantiate-and-run.mjs +15 -1
- package/built-in-skills/role-builder/scripts/operation-harness.mjs +6 -4
- package/built-in-skills/skill-creator/LICENSE.prismer +21 -0
- package/built-in-skills/skill-creator/NOTICE.md +18 -0
- package/built-in-skills/skill-creator/SKILL.md +20 -1
- package/built-in-skills/skill-creator/eval-viewer/generate_review.py +20 -31
- package/built-in-skills/skill-creator/eval-viewer/viewer.html +35 -23
- package/built-in-skills/skill-creator/references/hermes-agent-skill-authoring/GUIDE.md +220 -0
- package/built-in-skills/skill-creator/references/hermes-agent-skill-authoring/LICENSE +21 -0
- package/built-in-skills/{skill-authoring/SKILL.md → skill-creator/references/skill-authoring/GUIDE.md} +1 -1
- package/built-in-skills/{skill-builder/SKILL.md → skill-creator/references/skill-builder/GUIDE.md} +8 -12
- package/built-in-skills/skill-creator/references/skill-builder/scripts/ingest.mjs +93 -0
- package/built-in-skills/skill-creator/scripts/aggregate_benchmark.py +3 -2
- package/built-in-skills/skill-creator/scripts/import-library.mjs +30 -7
- package/built-in-skills/skill-creator/scripts/improve_description.py +14 -16
- package/built-in-skills/skill-creator/scripts/package_skill.py +16 -9
- package/built-in-skills/skill-creator/scripts/quick_validate.py +22 -5
- package/built-in-skills/skill-creator/scripts/run_eval.py +63 -144
- package/built-in-skills/skill-creator/scripts/run_loop.py +24 -16
- package/built-in-skills/slack-gif-creator/core/gif_builder.py +13 -2
- package/built-in-skills/slack-gif-creator/core/validators.py +17 -7
- package/built-in-skills/tasks/SKILL.md +2 -2
- package/built-in-skills/tdd/SKILL.md +7 -0
- package/built-in-skills/tdd/references/test-driven-development/GUIDE.md +356 -0
- package/built-in-skills/tdd/references/test-driven-development/LICENSE +21 -0
- package/built-in-skills/tdd/references/test-driven-development/LICENSE.superpowers +21 -0
- package/built-in-skills/tdd/references/test-driven-development/NOTICE.md +37 -0
- package/built-in-skills/team/SKILL.md +1 -1
- package/built-in-skills/web-artifacts-builder/SKILL.md +3 -3
- package/built-in-skills/web-artifacts-builder/scripts/bundle-artifact.sh +9 -7
- package/built-in-skills/web-artifacts-builder/scripts/init-artifact.sh +28 -55
- package/built-in-skills/webapp-qa/LICENSE +21 -0
- package/built-in-skills/webapp-qa/NOTICE.md +37 -0
- package/built-in-skills/webapp-qa/SKILL.md +41 -0
- package/built-in-skills/webapp-qa/references/dogfood/GUIDE.md +161 -0
- package/built-in-skills/webapp-qa/references/issue-taxonomy.md +109 -0
- package/built-in-skills/webapp-qa/templates/dogfood-report-template.md +86 -0
- package/built-in-skills/webapp-testing/SKILL.md +9 -2
- package/built-in-skills/webapp-testing/examples/console_logging.py +10 -4
- package/built-in-skills/webapp-testing/examples/element_discovery.py +11 -4
- package/built-in-skills/webapp-testing/examples/static_html_automation.py +14 -9
- package/built-in-skills/webapp-testing/scripts/with_server.py +34 -10
- package/built-in-skills/weekly-review/LICENSE +21 -0
- package/built-in-skills/weekly-review/NOTICE.md +21 -0
- package/built-in-skills/weekly-review/SKILL.md +49 -0
- package/components/eaas-pi-components.manifest.json +37 -0
- package/dist/cli.cjs +7017 -3340
- package/dist/cli.js +6631 -2936
- package/dist/index.cjs +7863 -3830
- package/dist/index.d.cts +459 -5
- package/dist/index.d.ts +459 -5
- package/dist/index.js +7106 -3064
- package/package.json +7 -2
- package/plugins/memory/prismer/__init__.py +4 -0
- package/plugins/memory/prismer/tool-schemas.generated.json +8 -0
- package/built-in-skills/skill-builder/scripts/ingest.mjs +0 -265
- package/built-in-skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- /package/built-in-skills/{document-generation/SKILL.md → office-artifacts/references/document-generation/GUIDE.md} +0 -0
- /package/built-in-skills/{skill-authoring/skill.json → skill-creator/references/skill-authoring/draft-contract.json} +0 -0
|
@@ -0,0 +1,1408 @@
|
|
|
1
|
+
# Shader Pipeline & Composable Effects
|
|
2
|
+
|
|
3
|
+
Post-processing effects applied to the pixel canvas (`numpy uint8 array, shape (H,W,3)`) after character rendering and before encoding. Also covers **pixel-level blend modes**, **feedback buffers**, and the **ShaderChain** compositor.
|
|
4
|
+
|
|
5
|
+
> **See also:** composition.md (blend modes, tonemap) · effects.md · scenes.md · architecture.md · optimization.md · troubleshooting.md
|
|
6
|
+
>
|
|
7
|
+
> **Blend modes:** For the 20 pixel blend modes and `blend_canvas()`, see `composition.md`. All blending uses `blend_canvas(base, top, mode, opacity)`.
|
|
8
|
+
|
|
9
|
+
## Design Philosophy
|
|
10
|
+
|
|
11
|
+
The shader pipeline turns raw ASCII renders into cinematic output. The system is designed for **composability** — every shader, blend mode, and feedback transform is an independent building block. Combining them creates infinite visual variety from a small set of primitives.
|
|
12
|
+
|
|
13
|
+
Choose shaders that reinforce the mood:
|
|
14
|
+
- **Retro terminal**: CRT + scanlines + grain + green/amber tint
|
|
15
|
+
- **Clean modern**: light bloom + subtle vignette only
|
|
16
|
+
- **Glitch art**: heavy chromatic aberration + glitch bands + color wobble + pixel sort
|
|
17
|
+
- **Cinematic**: bloom + vignette + grain + color grade
|
|
18
|
+
- **Dreamy**: heavy bloom + soft focus + color wobble + low contrast
|
|
19
|
+
- **Harsh/industrial**: high contrast + grain + scanlines + no bloom
|
|
20
|
+
- **Psychedelic**: color wobble + chromatic + kaleidoscope mirror + high saturation + feedback with hue shift
|
|
21
|
+
- **Data corruption**: pixel sort + data bend + block glitch + posterize
|
|
22
|
+
- **Recursive/infinite**: feedback buffer with zoom + screen blend + hue shift
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Pixel-Level Blend Modes
|
|
27
|
+
|
|
28
|
+
All operate on float32 [0,1] canvases for precision. Use `blend_canvas(base, top, mode, opacity)` which handles uint8 <-> float conversion.
|
|
29
|
+
|
|
30
|
+
### Available Modes
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
BLEND_MODES = {
|
|
34
|
+
"normal": lambda a, b: b,
|
|
35
|
+
"add": lambda a, b: np.clip(a + b, 0, 1),
|
|
36
|
+
"subtract": lambda a, b: np.clip(a - b, 0, 1),
|
|
37
|
+
"multiply": lambda a, b: a * b,
|
|
38
|
+
"screen": lambda a, b: 1 - (1-a)*(1-b),
|
|
39
|
+
"overlay": # 2*a*b if a<0.5, else 1-2*(1-a)*(1-b)
|
|
40
|
+
"softlight": lambda a, b: (1-2*b)*a*a + 2*b*a,
|
|
41
|
+
"hardlight": # like overlay but keyed on b
|
|
42
|
+
"difference": lambda a, b: abs(a - b),
|
|
43
|
+
"exclusion": lambda a, b: a + b - 2*a*b,
|
|
44
|
+
"colordodge": lambda a, b: a / (1-b),
|
|
45
|
+
"colorburn": lambda a, b: 1 - (1-a)/b,
|
|
46
|
+
"linearlight": lambda a, b: a + 2*b - 1,
|
|
47
|
+
"vividlight": # burn if b<0.5, dodge if b>=0.5
|
|
48
|
+
"pin_light": # min(a,2b) if b<0.5, max(a,2b-1) if b>=0.5
|
|
49
|
+
"hard_mix": lambda a, b: 1 if a+b>=1 else 0,
|
|
50
|
+
"lighten": lambda a, b: max(a, b),
|
|
51
|
+
"darken": lambda a, b: min(a, b),
|
|
52
|
+
"grain_extract": lambda a, b: a - b + 0.5,
|
|
53
|
+
"grain_merge": lambda a, b: a + b - 0.5,
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Usage
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
def blend_canvas(base, top, mode="normal", opacity=1.0):
|
|
61
|
+
"""Blend two uint8 canvases (H,W,3) using a named blend mode + opacity."""
|
|
62
|
+
af = base.astype(np.float32) / 255.0
|
|
63
|
+
bf = top.astype(np.float32) / 255.0
|
|
64
|
+
result = BLEND_MODES[mode](af, bf)
|
|
65
|
+
if opacity < 1.0:
|
|
66
|
+
result = af * (1-opacity) + result * opacity
|
|
67
|
+
return np.clip(result * 255, 0, 255).astype(np.uint8)
|
|
68
|
+
|
|
69
|
+
# Multi-layer compositing
|
|
70
|
+
result = blend_canvas(base, layer_a, "screen", 0.7)
|
|
71
|
+
result = blend_canvas(result, layer_b, "difference", 0.5)
|
|
72
|
+
result = blend_canvas(result, layer_c, "multiply", 0.3)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Creative Combinations
|
|
76
|
+
|
|
77
|
+
- **Feedback + difference** = psychedelic color evolution (each frame XORs with the previous)
|
|
78
|
+
- **Screen + screen** = additive glow stacking
|
|
79
|
+
- **Multiply** on two different effects = only shows where both have brightness (intersection)
|
|
80
|
+
- **Exclusion** between two layers = creates complementary patterns where they differ
|
|
81
|
+
- **Color dodge/burn** = extreme contrast enhancement at overlap zones
|
|
82
|
+
- **Hard mix** = reduces everything to pure black/white/color at intersections
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Feedback Buffer
|
|
87
|
+
|
|
88
|
+
Recursive temporal effect: frame N-1 feeds back into frame N with decay and optional spatial transform. Creates trails, echoes, smearing, zoom tunnels, rotation feedback, rainbow trails.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
class FeedbackBuffer:
|
|
92
|
+
def __init__(self):
|
|
93
|
+
self.buf = None # previous frame (float32, 0-1)
|
|
94
|
+
|
|
95
|
+
def apply(self, canvas, decay=0.85, blend="screen", opacity=0.5,
|
|
96
|
+
transform=None, transform_amt=0.02, hue_shift=0.0):
|
|
97
|
+
"""Mix current frame with decayed/transformed previous frame.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
canvas: current frame (uint8 H,W,3)
|
|
101
|
+
decay: how fast old frame fades (0=instant, 1=permanent)
|
|
102
|
+
blend: blend mode for mixing feedback
|
|
103
|
+
opacity: strength of feedback mix
|
|
104
|
+
transform: None, "zoom", "shrink", "rotate_cw", "rotate_ccw",
|
|
105
|
+
"shift_up", "shift_down", "mirror_h"
|
|
106
|
+
transform_amt: strength of spatial transform per frame
|
|
107
|
+
hue_shift: rotate hue of feedback buffer each frame (0-1)
|
|
108
|
+
"""
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Feedback Presets
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# Infinite zoom tunnel
|
|
115
|
+
fb_cfg = {"decay": 0.8, "blend": "screen", "opacity": 0.4,
|
|
116
|
+
"transform": "zoom", "transform_amt": 0.015}
|
|
117
|
+
|
|
118
|
+
# Rainbow trails (psychedelic)
|
|
119
|
+
fb_cfg = {"decay": 0.7, "blend": "screen", "opacity": 0.3,
|
|
120
|
+
"transform": "zoom", "transform_amt": 0.01, "hue_shift": 0.02}
|
|
121
|
+
|
|
122
|
+
# Ghostly echo (horror)
|
|
123
|
+
fb_cfg = {"decay": 0.9, "blend": "add", "opacity": 0.15,
|
|
124
|
+
"transform": "shift_up", "transform_amt": 0.01}
|
|
125
|
+
|
|
126
|
+
# Kaleidoscopic recursion
|
|
127
|
+
fb_cfg = {"decay": 0.75, "blend": "screen", "opacity": 0.35,
|
|
128
|
+
"transform": "rotate_cw", "transform_amt": 0.005, "hue_shift": 0.01}
|
|
129
|
+
|
|
130
|
+
# Color evolution (abstract)
|
|
131
|
+
fb_cfg = {"decay": 0.8, "blend": "difference", "opacity": 0.4, "hue_shift": 0.03}
|
|
132
|
+
|
|
133
|
+
# Multiplied depth
|
|
134
|
+
fb_cfg = {"decay": 0.65, "blend": "multiply", "opacity": 0.3, "transform": "mirror_h"}
|
|
135
|
+
|
|
136
|
+
# Rising heat haze
|
|
137
|
+
fb_cfg = {"decay": 0.5, "blend": "add", "opacity": 0.2,
|
|
138
|
+
"transform": "shift_up", "transform_amt": 0.02}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## ShaderChain
|
|
144
|
+
|
|
145
|
+
Composable shader pipeline. Build chains of named shaders with parameters. Order matters — shaders are applied sequentially to the canvas.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
class ShaderChain:
|
|
149
|
+
"""Composable shader pipeline.
|
|
150
|
+
|
|
151
|
+
Usage:
|
|
152
|
+
chain = ShaderChain()
|
|
153
|
+
chain.add("bloom", thr=120)
|
|
154
|
+
chain.add("chromatic", amt=5)
|
|
155
|
+
chain.add("kaleidoscope", folds=6)
|
|
156
|
+
chain.add("vignette", s=0.2)
|
|
157
|
+
chain.add("grain", amt=12)
|
|
158
|
+
canvas = chain.apply(canvas, f=features, t=time)
|
|
159
|
+
"""
|
|
160
|
+
def __init__(self):
|
|
161
|
+
self.steps = []
|
|
162
|
+
|
|
163
|
+
def add(self, shader_name, **kwargs):
|
|
164
|
+
self.steps.append((shader_name, kwargs))
|
|
165
|
+
return self # chainable
|
|
166
|
+
|
|
167
|
+
def apply(self, canvas, f=None, t=0):
|
|
168
|
+
if f is None: f = {}
|
|
169
|
+
for name, kwargs in self.steps:
|
|
170
|
+
canvas = _apply_shader_step(canvas, name, kwargs, f, t)
|
|
171
|
+
return canvas
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### `_apply_shader_step()` — Full Dispatch Function
|
|
175
|
+
|
|
176
|
+
Routes shader names to implementations. Some shaders have **audio-reactive scaling** — the dispatch function reads `f["bdecay"]` and `f["rms"]` to modulate parameters on the beat.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
def _apply_shader_step(canvas, name, kwargs, f, t):
|
|
180
|
+
"""Dispatch a single shader by name with kwargs.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
canvas: uint8 (H,W,3) pixel array
|
|
184
|
+
name: shader key string (e.g. "bloom", "chromatic")
|
|
185
|
+
kwargs: dict of shader parameters
|
|
186
|
+
f: audio features dict (keys: bdecay, rms, sub, etc.)
|
|
187
|
+
t: current time in seconds (float)
|
|
188
|
+
Returns:
|
|
189
|
+
canvas: uint8 (H,W,3) — processed
|
|
190
|
+
"""
|
|
191
|
+
bd = f.get("bdecay", 0) # beat decay (0-1, high on beat)
|
|
192
|
+
rms = f.get("rms", 0.3) # audio energy (0-1)
|
|
193
|
+
|
|
194
|
+
# --- Geometry ---
|
|
195
|
+
if name == "crt":
|
|
196
|
+
return sh_crt(canvas, kwargs.get("strength", 0.05))
|
|
197
|
+
elif name == "pixelate":
|
|
198
|
+
return sh_pixelate(canvas, kwargs.get("block", 4))
|
|
199
|
+
elif name == "wave_distort":
|
|
200
|
+
return sh_wave_distort(canvas, t,
|
|
201
|
+
kwargs.get("freq", 0.02), kwargs.get("amp", 8), kwargs.get("axis", "x"))
|
|
202
|
+
elif name == "kaleidoscope":
|
|
203
|
+
return sh_kaleidoscope(canvas.copy(), kwargs.get("folds", 6))
|
|
204
|
+
elif name == "mirror_h":
|
|
205
|
+
return sh_mirror_h(canvas.copy())
|
|
206
|
+
elif name == "mirror_v":
|
|
207
|
+
return sh_mirror_v(canvas.copy())
|
|
208
|
+
elif name == "mirror_quad":
|
|
209
|
+
return sh_mirror_quad(canvas.copy())
|
|
210
|
+
elif name == "mirror_diag":
|
|
211
|
+
return sh_mirror_diag(canvas.copy())
|
|
212
|
+
|
|
213
|
+
# --- Channel ---
|
|
214
|
+
elif name == "chromatic":
|
|
215
|
+
base = kwargs.get("amt", 3)
|
|
216
|
+
return sh_chromatic(canvas, max(1, int(base * (0.4 + bd * 0.8))))
|
|
217
|
+
elif name == "channel_shift":
|
|
218
|
+
return sh_channel_shift(canvas,
|
|
219
|
+
kwargs.get("r", (0,0)), kwargs.get("g", (0,0)), kwargs.get("b", (0,0)))
|
|
220
|
+
elif name == "channel_swap":
|
|
221
|
+
return sh_channel_swap(canvas, kwargs.get("order", (2,1,0)))
|
|
222
|
+
elif name == "rgb_split_radial":
|
|
223
|
+
return sh_rgb_split_radial(canvas, kwargs.get("strength", 5))
|
|
224
|
+
|
|
225
|
+
# --- Color ---
|
|
226
|
+
elif name == "invert":
|
|
227
|
+
return sh_invert(canvas)
|
|
228
|
+
elif name == "posterize":
|
|
229
|
+
return sh_posterize(canvas, kwargs.get("levels", 4))
|
|
230
|
+
elif name == "threshold":
|
|
231
|
+
return sh_threshold(canvas, kwargs.get("thr", 128))
|
|
232
|
+
elif name == "solarize":
|
|
233
|
+
return sh_solarize(canvas, kwargs.get("threshold", 128))
|
|
234
|
+
elif name == "hue_rotate":
|
|
235
|
+
return sh_hue_rotate(canvas, kwargs.get("amount", 0.1))
|
|
236
|
+
elif name == "saturation":
|
|
237
|
+
return sh_saturation(canvas, kwargs.get("factor", 1.5))
|
|
238
|
+
elif name == "color_grade":
|
|
239
|
+
return sh_color_grade(canvas, kwargs.get("tint", (1,1,1)))
|
|
240
|
+
elif name == "color_wobble":
|
|
241
|
+
return sh_color_wobble(canvas, t, kwargs.get("amt", 0.3) * (0.5 + rms * 0.8))
|
|
242
|
+
elif name == "color_ramp":
|
|
243
|
+
return sh_color_ramp(canvas, kwargs.get("ramp", [(0,0,0),(255,255,255)]))
|
|
244
|
+
|
|
245
|
+
# --- Glow / Blur ---
|
|
246
|
+
elif name == "bloom":
|
|
247
|
+
return sh_bloom(canvas, kwargs.get("thr", 130))
|
|
248
|
+
elif name == "edge_glow":
|
|
249
|
+
return sh_edge_glow(canvas, kwargs.get("hue", 0.5))
|
|
250
|
+
elif name == "soft_focus":
|
|
251
|
+
return sh_soft_focus(canvas, kwargs.get("strength", 0.3))
|
|
252
|
+
elif name == "radial_blur":
|
|
253
|
+
return sh_radial_blur(canvas, kwargs.get("strength", 0.03))
|
|
254
|
+
|
|
255
|
+
# --- Noise ---
|
|
256
|
+
elif name == "grain":
|
|
257
|
+
return sh_grain(canvas, int(kwargs.get("amt", 10) * (0.5 + rms * 0.8)))
|
|
258
|
+
elif name == "static":
|
|
259
|
+
return sh_static_noise(canvas, kwargs.get("density", 0.05), kwargs.get("color", True))
|
|
260
|
+
|
|
261
|
+
# --- Lines / Patterns ---
|
|
262
|
+
elif name == "scanlines":
|
|
263
|
+
return sh_scanlines(canvas, kwargs.get("intensity", 0.08), kwargs.get("spacing", 3))
|
|
264
|
+
elif name == "halftone":
|
|
265
|
+
return sh_halftone(canvas, kwargs.get("dot_size", 6))
|
|
266
|
+
|
|
267
|
+
# --- Tone ---
|
|
268
|
+
elif name == "vignette":
|
|
269
|
+
return sh_vignette(canvas, kwargs.get("s", 0.22))
|
|
270
|
+
elif name == "contrast":
|
|
271
|
+
return sh_contrast(canvas, kwargs.get("factor", 1.3))
|
|
272
|
+
elif name == "gamma":
|
|
273
|
+
return sh_gamma(canvas, kwargs.get("gamma", 1.5))
|
|
274
|
+
elif name == "levels":
|
|
275
|
+
return sh_levels(canvas,
|
|
276
|
+
kwargs.get("black", 0), kwargs.get("white", 255), kwargs.get("midtone", 1.0))
|
|
277
|
+
elif name == "brightness":
|
|
278
|
+
return sh_brightness(canvas, kwargs.get("factor", 1.5))
|
|
279
|
+
|
|
280
|
+
# --- Glitch / Data ---
|
|
281
|
+
elif name == "glitch_bands":
|
|
282
|
+
return sh_glitch_bands(canvas, f)
|
|
283
|
+
elif name == "block_glitch":
|
|
284
|
+
return sh_block_glitch(canvas, kwargs.get("n_blocks", 8), kwargs.get("max_size", 40))
|
|
285
|
+
elif name == "pixel_sort":
|
|
286
|
+
return sh_pixel_sort(canvas, kwargs.get("threshold", 100), kwargs.get("direction", "h"))
|
|
287
|
+
elif name == "data_bend":
|
|
288
|
+
return sh_data_bend(canvas, kwargs.get("offset", 1000), kwargs.get("chunk", 500))
|
|
289
|
+
|
|
290
|
+
else:
|
|
291
|
+
return canvas # unknown shader — passthrough
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Audio-Reactive Shaders
|
|
295
|
+
|
|
296
|
+
Three shaders scale their parameters based on audio features:
|
|
297
|
+
|
|
298
|
+
| Shader | Reactive To | Effect |
|
|
299
|
+
|--------|------------|--------|
|
|
300
|
+
| `chromatic` | `bdecay` | `amt * (0.4 + bdecay * 0.8)` — aberration kicks on beats |
|
|
301
|
+
| `color_wobble` | `rms` | `amt * (0.5 + rms * 0.8)` — wobble intensity follows energy |
|
|
302
|
+
| `grain` | `rms` | `amt * (0.5 + rms * 0.8)` — grain rougher in loud sections |
|
|
303
|
+
| `glitch_bands` | `bdecay`, `sub` | Number of bands and displacement scale with beat energy |
|
|
304
|
+
|
|
305
|
+
To make any shader beat-reactive, scale its parameter in the dispatch: `base_val * (low + bd * range)`.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Full Shader Catalog
|
|
310
|
+
|
|
311
|
+
### Geometry Shaders
|
|
312
|
+
|
|
313
|
+
| Shader | Key Params | Description |
|
|
314
|
+
|--------|-----------|-------------|
|
|
315
|
+
| `crt` | `strength=0.05` | CRT barrel distortion (cached remap) |
|
|
316
|
+
| `pixelate` | `block=4` | Reduce effective resolution |
|
|
317
|
+
| `wave_distort` | `freq, amp, axis` | Sinusoidal row/column displacement |
|
|
318
|
+
| `kaleidoscope` | `folds=6` | Radial symmetry via polar remapping |
|
|
319
|
+
| `mirror_h` | — | Horizontal mirror |
|
|
320
|
+
| `mirror_v` | — | Vertical mirror |
|
|
321
|
+
| `mirror_quad` | — | 4-fold mirror |
|
|
322
|
+
| `mirror_diag` | — | Diagonal mirror |
|
|
323
|
+
|
|
324
|
+
### Channel Manipulation
|
|
325
|
+
|
|
326
|
+
| Shader | Key Params | Description |
|
|
327
|
+
|--------|-----------|-------------|
|
|
328
|
+
| `chromatic` | `amt=3` | R/B channel horizontal shift (beat-reactive) |
|
|
329
|
+
| `channel_shift` | `r=(sx,sy), g, b` | Independent per-channel x,y shifting |
|
|
330
|
+
| `channel_swap` | `order=(2,1,0)` | Reorder RGB channels (BGR, GRB, etc.) |
|
|
331
|
+
| `rgb_split_radial` | `strength=5` | Chromatic aberration radiating from center |
|
|
332
|
+
|
|
333
|
+
### Color Manipulation
|
|
334
|
+
|
|
335
|
+
| Shader | Key Params | Description |
|
|
336
|
+
|--------|-----------|-------------|
|
|
337
|
+
| `invert` | — | Negate all colors |
|
|
338
|
+
| `posterize` | `levels=4` | Reduce color depth to N levels |
|
|
339
|
+
| `threshold` | `thr=128` | Binary black/white |
|
|
340
|
+
| `solarize` | `threshold=128` | Invert pixels above threshold |
|
|
341
|
+
| `hue_rotate` | `amount=0.1` | Rotate all hues by amount (0-1) |
|
|
342
|
+
| `saturation` | `factor=1.5` | Scale saturation (>1=more, <1=less) |
|
|
343
|
+
| `color_grade` | `tint=(r,g,b)` | Per-channel multiplier |
|
|
344
|
+
| `color_wobble` | `amt=0.3` | Time-varying per-channel sine modulation |
|
|
345
|
+
| `color_ramp` | `ramp=[(R,G,B),...]` | Map luminance to custom color gradient |
|
|
346
|
+
|
|
347
|
+
### Glow / Blur
|
|
348
|
+
|
|
349
|
+
| Shader | Key Params | Description |
|
|
350
|
+
|--------|-----------|-------------|
|
|
351
|
+
| `bloom` | `thr=130` | Bright area glow (4x downsample + box blur) |
|
|
352
|
+
| `edge_glow` | `hue=0.5` | Detect edges, add colored overlay |
|
|
353
|
+
| `soft_focus` | `strength=0.3` | Blend with blurred version |
|
|
354
|
+
| `radial_blur` | `strength=0.03` | Zoom blur from center outward |
|
|
355
|
+
|
|
356
|
+
### Noise / Grain
|
|
357
|
+
|
|
358
|
+
| Shader | Key Params | Description |
|
|
359
|
+
|--------|-----------|-------------|
|
|
360
|
+
| `grain` | `amt=10` | 2x-downsampled film grain (beat-reactive) |
|
|
361
|
+
| `static` | `density=0.05, color=True` | Random pixel noise (TV static) |
|
|
362
|
+
|
|
363
|
+
### Lines / Patterns
|
|
364
|
+
|
|
365
|
+
| Shader | Key Params | Description |
|
|
366
|
+
|--------|-----------|-------------|
|
|
367
|
+
| `scanlines` | `intensity=0.08, spacing=3` | Darken every Nth row |
|
|
368
|
+
| `halftone` | `dot_size=6` | Halftone dot pattern overlay |
|
|
369
|
+
|
|
370
|
+
### Tone
|
|
371
|
+
|
|
372
|
+
| Shader | Key Params | Description |
|
|
373
|
+
|--------|-----------|-------------|
|
|
374
|
+
| `vignette` | `s=0.22` | Edge darkening (cached distance field) |
|
|
375
|
+
| `contrast` | `factor=1.3` | Adjust contrast around midpoint 128 |
|
|
376
|
+
| `gamma` | `gamma=1.5` | Gamma correction (>1=brighter mids) |
|
|
377
|
+
| `levels` | `black, white, midtone` | Levels adjustment (Photoshop-style) |
|
|
378
|
+
| `brightness` | `factor=1.5` | Global brightness multiplier |
|
|
379
|
+
|
|
380
|
+
### Glitch / Data
|
|
381
|
+
|
|
382
|
+
| Shader | Key Params | Description |
|
|
383
|
+
|--------|-----------|-------------|
|
|
384
|
+
| `glitch_bands` | (uses `f`) | Beat-reactive horizontal row displacement |
|
|
385
|
+
| `block_glitch` | `n_blocks=8, max_size=40` | Random rectangular block displacement |
|
|
386
|
+
| `pixel_sort` | `threshold=100, direction="h"` | Sort pixels by brightness in rows/columns |
|
|
387
|
+
| `data_bend` | `offset, chunk` | Raw byte displacement (datamoshing) |
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Shader Implementations
|
|
392
|
+
|
|
393
|
+
Every shader function takes a canvas (`uint8 H,W,3`) and returns a canvas of the same shape. The naming convention is `sh_<name>`. Geometry shaders that build coordinate remap tables should **cache** them since the table only depends on resolution + parameters, not on frame content.
|
|
394
|
+
|
|
395
|
+
### Helpers
|
|
396
|
+
|
|
397
|
+
Shaders that manipulate hue/saturation need vectorized HSV conversion:
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
def rgb2hsv(r, g, b):
|
|
401
|
+
"""Vectorized RGB (0-255 uint8) -> HSV (float32 0-1)."""
|
|
402
|
+
rf = r.astype(np.float32) / 255.0
|
|
403
|
+
gf = g.astype(np.float32) / 255.0
|
|
404
|
+
bf = b.astype(np.float32) / 255.0
|
|
405
|
+
cmax = np.maximum(np.maximum(rf, gf), bf)
|
|
406
|
+
cmin = np.minimum(np.minimum(rf, gf), bf)
|
|
407
|
+
delta = cmax - cmin + 1e-10
|
|
408
|
+
h = np.zeros_like(rf)
|
|
409
|
+
m = cmax == rf; h[m] = ((gf[m] - bf[m]) / delta[m]) % 6
|
|
410
|
+
m = cmax == gf; h[m] = (bf[m] - rf[m]) / delta[m] + 2
|
|
411
|
+
m = cmax == bf; h[m] = (rf[m] - gf[m]) / delta[m] + 4
|
|
412
|
+
h = h / 6.0 % 1.0
|
|
413
|
+
s = np.where(cmax > 0, delta / (cmax + 1e-10), 0)
|
|
414
|
+
return h, s, cmax
|
|
415
|
+
|
|
416
|
+
def hsv2rgb(h, s, v):
|
|
417
|
+
"""Vectorized HSV->RGB. h,s,v are numpy float32 arrays."""
|
|
418
|
+
h = h % 1.0
|
|
419
|
+
c = v * s; x = c * (1 - np.abs((h * 6) % 2 - 1)); m = v - c
|
|
420
|
+
r = np.zeros_like(h); g = np.zeros_like(h); b = np.zeros_like(h)
|
|
421
|
+
mask = h < 1/6; r[mask]=c[mask]; g[mask]=x[mask]
|
|
422
|
+
mask = (h>=1/6)&(h<2/6); r[mask]=x[mask]; g[mask]=c[mask]
|
|
423
|
+
mask = (h>=2/6)&(h<3/6); g[mask]=c[mask]; b[mask]=x[mask]
|
|
424
|
+
mask = (h>=3/6)&(h<4/6); g[mask]=x[mask]; b[mask]=c[mask]
|
|
425
|
+
mask = (h>=4/6)&(h<5/6); r[mask]=x[mask]; b[mask]=c[mask]
|
|
426
|
+
mask = h >= 5/6; r[mask]=c[mask]; b[mask]=x[mask]
|
|
427
|
+
R = np.clip((r+m)*255, 0, 255).astype(np.uint8)
|
|
428
|
+
G = np.clip((g+m)*255, 0, 255).astype(np.uint8)
|
|
429
|
+
B = np.clip((b+m)*255, 0, 255).astype(np.uint8)
|
|
430
|
+
return R, G, B
|
|
431
|
+
|
|
432
|
+
def mkc(R, G, B, rows, cols):
|
|
433
|
+
"""Stack R,G,B uint8 arrays into (rows,cols,3) canvas."""
|
|
434
|
+
o = np.zeros((rows, cols, 3), dtype=np.uint8)
|
|
435
|
+
o[:,:,0] = R; o[:,:,1] = G; o[:,:,2] = B
|
|
436
|
+
return o
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
### Geometry Shaders
|
|
442
|
+
|
|
443
|
+
#### CRT Barrel Distortion
|
|
444
|
+
Cache the coordinate remap — it never changes per frame:
|
|
445
|
+
```python
|
|
446
|
+
_crt_cache = {}
|
|
447
|
+
def sh_crt(c, strength=0.05):
|
|
448
|
+
k = (c.shape[0], c.shape[1], round(strength, 3))
|
|
449
|
+
if k not in _crt_cache:
|
|
450
|
+
h, w = c.shape[:2]; cy, cx = h/2, w/2
|
|
451
|
+
Y = np.arange(h, dtype=np.float32)[:, None]
|
|
452
|
+
X = np.arange(w, dtype=np.float32)[None, :]
|
|
453
|
+
ny = (Y - cy) / cy; nx = (X - cx) / cx
|
|
454
|
+
r2 = nx**2 + ny**2
|
|
455
|
+
factor = 1 + strength * r2
|
|
456
|
+
sx = np.clip((nx * factor * cx + cx), 0, w-1).astype(np.int32)
|
|
457
|
+
sy = np.clip((ny * factor * cy + cy), 0, h-1).astype(np.int32)
|
|
458
|
+
_crt_cache[k] = (sy, sx)
|
|
459
|
+
sy, sx = _crt_cache[k]
|
|
460
|
+
return c[sy, sx]
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### Pixelate
|
|
464
|
+
```python
|
|
465
|
+
def sh_pixelate(c, block=4):
|
|
466
|
+
"""Reduce effective resolution."""
|
|
467
|
+
sm = c[::block, ::block]
|
|
468
|
+
return np.repeat(np.repeat(sm, block, axis=0), block, axis=1)[:c.shape[0], :c.shape[1]]
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### Wave Distort
|
|
472
|
+
```python
|
|
473
|
+
def sh_wave_distort(c, t, freq=0.02, amp=8, axis="x"):
|
|
474
|
+
"""Sinusoidal row/column displacement. Uses time t for animation."""
|
|
475
|
+
h, w = c.shape[:2]
|
|
476
|
+
out = c.copy()
|
|
477
|
+
if axis == "x":
|
|
478
|
+
for y in range(h):
|
|
479
|
+
shift = int(amp * math.sin(y * freq + t * 3))
|
|
480
|
+
out[y] = np.roll(c[y], shift, axis=0)
|
|
481
|
+
else:
|
|
482
|
+
for x in range(w):
|
|
483
|
+
shift = int(amp * math.sin(x * freq + t * 3))
|
|
484
|
+
out[:, x] = np.roll(c[:, x], shift, axis=0)
|
|
485
|
+
return out
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### Displacement Map
|
|
489
|
+
```python
|
|
490
|
+
def sh_displacement_map(c, dx_map, dy_map, strength=10):
|
|
491
|
+
"""Displace pixels using float32 displacement maps (same HxW as c).
|
|
492
|
+
dx_map/dy_map: positive = shift right/down."""
|
|
493
|
+
h, w = c.shape[:2]
|
|
494
|
+
Y = np.arange(h)[:, None]; X = np.arange(w)[None, :]
|
|
495
|
+
ny = np.clip((Y + (dy_map * strength).astype(int)), 0, h-1)
|
|
496
|
+
nx = np.clip((X + (dx_map * strength).astype(int)), 0, w-1)
|
|
497
|
+
return c[ny, nx]
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
#### Kaleidoscope
|
|
501
|
+
```python
|
|
502
|
+
def sh_kaleidoscope(c, folds=6):
|
|
503
|
+
"""Radial symmetry by polar coordinate remapping."""
|
|
504
|
+
h, w = c.shape[:2]; cy, cx = h//2, w//2
|
|
505
|
+
Y = np.arange(h, dtype=np.float32)[:, None] - cy
|
|
506
|
+
X = np.arange(w, dtype=np.float32)[None, :] - cx
|
|
507
|
+
angle = np.arctan2(Y, X)
|
|
508
|
+
dist = np.sqrt(X**2 + Y**2)
|
|
509
|
+
wedge = 2 * np.pi / folds
|
|
510
|
+
folded_angle = np.abs((angle % wedge) - wedge/2)
|
|
511
|
+
ny = np.clip((cy + dist * np.sin(folded_angle)).astype(int), 0, h-1)
|
|
512
|
+
nx = np.clip((cx + dist * np.cos(folded_angle)).astype(int), 0, w-1)
|
|
513
|
+
return c[ny, nx]
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
#### Mirror Variants
|
|
517
|
+
```python
|
|
518
|
+
def sh_mirror_h(c):
|
|
519
|
+
"""Horizontal mirror — left half reflected to right."""
|
|
520
|
+
w = c.shape[1]; half = w // 2
|
|
521
|
+
c[:, w-half:] = c[:, :half][:, ::-1]
|
|
522
|
+
return c
|
|
523
|
+
|
|
524
|
+
def sh_mirror_v(c):
|
|
525
|
+
"""Vertical mirror — top half reflected to bottom."""
|
|
526
|
+
h = c.shape[0]; half = h // 2
|
|
527
|
+
c[h-half:, :] = c[:half, :][::-1, :]
|
|
528
|
+
return c
|
|
529
|
+
|
|
530
|
+
def sh_mirror_quad(c):
|
|
531
|
+
"""4-fold mirror — top-left quadrant reflected to all four."""
|
|
532
|
+
return sh_mirror_v(sh_mirror_h(c))
|
|
533
|
+
|
|
534
|
+
def sh_mirror_diag(c):
|
|
535
|
+
"""Diagonal mirror — top-left triangle reflected."""
|
|
536
|
+
h, w = c.shape[:2]
|
|
537
|
+
for y in range(h):
|
|
538
|
+
x_cut = int(w * y / h)
|
|
539
|
+
if x_cut > 0 and x_cut < w:
|
|
540
|
+
c[y, x_cut:] = c[y, :x_cut+1][::-1][:w-x_cut]
|
|
541
|
+
return c
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
> **Note:** Mirror shaders mutate in-place. The dispatch function passes `canvas.copy()` to avoid corrupting the original.
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
### Channel Manipulation Shaders
|
|
549
|
+
|
|
550
|
+
#### Chromatic Aberration
|
|
551
|
+
```python
|
|
552
|
+
def sh_chromatic(c, amt=3):
|
|
553
|
+
"""R/B channel horizontal shift. Beat-reactive in dispatch (amt scaled by bdecay)."""
|
|
554
|
+
if amt < 1: return c
|
|
555
|
+
a = int(amt)
|
|
556
|
+
o = c.copy()
|
|
557
|
+
o[:, a:, 0] = c[:, :-a, 0] # red shifts right
|
|
558
|
+
o[:, :-a, 2] = c[:, a:, 2] # blue shifts left
|
|
559
|
+
return o
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
#### Channel Shift
|
|
563
|
+
```python
|
|
564
|
+
def sh_channel_shift(c, r_shift=(0,0), g_shift=(0,0), b_shift=(0,0)):
|
|
565
|
+
"""Independent per-channel x,y shifting."""
|
|
566
|
+
o = c.copy()
|
|
567
|
+
for ch_i, (sx, sy) in enumerate([r_shift, g_shift, b_shift]):
|
|
568
|
+
if sx != 0: o[:,:,ch_i] = np.roll(c[:,:,ch_i], sx, axis=1)
|
|
569
|
+
if sy != 0: o[:,:,ch_i] = np.roll(o[:,:,ch_i], sy, axis=0)
|
|
570
|
+
return o
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
#### Channel Swap
|
|
574
|
+
```python
|
|
575
|
+
def sh_channel_swap(c, order=(2,1,0)):
|
|
576
|
+
"""Reorder RGB channels. (2,1,0)=BGR, (1,0,2)=GRB, etc."""
|
|
577
|
+
return c[:, :, list(order)]
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### RGB Split Radial
|
|
581
|
+
```python
|
|
582
|
+
def sh_rgb_split_radial(c, strength=5):
|
|
583
|
+
"""Chromatic aberration radiating from center — stronger at edges."""
|
|
584
|
+
h, w = c.shape[:2]; cy, cx = h//2, w//2
|
|
585
|
+
Y = np.arange(h, dtype=np.float32)[:, None]
|
|
586
|
+
X = np.arange(w, dtype=np.float32)[None, :]
|
|
587
|
+
dist = np.sqrt((Y-cy)**2 + (X-cx)**2)
|
|
588
|
+
max_dist = np.sqrt(cy**2 + cx**2)
|
|
589
|
+
factor = dist / max_dist * strength
|
|
590
|
+
dy = ((Y-cy) / (dist+1) * factor).astype(int)
|
|
591
|
+
dx = ((X-cx) / (dist+1) * factor).astype(int)
|
|
592
|
+
out = c.copy()
|
|
593
|
+
ry = np.clip(Y.astype(int)+dy, 0, h-1); rx = np.clip(X.astype(int)+dx, 0, w-1)
|
|
594
|
+
out[:,:,0] = c[ry, rx, 0] # red shifts outward
|
|
595
|
+
by = np.clip(Y.astype(int)-dy, 0, h-1); bx = np.clip(X.astype(int)-dx, 0, w-1)
|
|
596
|
+
out[:,:,2] = c[by, bx, 2] # blue shifts inward
|
|
597
|
+
return out
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
### Color Manipulation Shaders
|
|
603
|
+
|
|
604
|
+
#### Invert
|
|
605
|
+
```python
|
|
606
|
+
def sh_invert(c):
|
|
607
|
+
return 255 - c
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
#### Posterize
|
|
611
|
+
```python
|
|
612
|
+
def sh_posterize(c, levels=4):
|
|
613
|
+
"""Reduce color depth to N levels per channel."""
|
|
614
|
+
step = 256.0 / levels
|
|
615
|
+
return (np.floor(c.astype(np.float32) / step) * step).astype(np.uint8)
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
#### Threshold
|
|
619
|
+
```python
|
|
620
|
+
def sh_threshold(c, thr=128):
|
|
621
|
+
"""Binary black/white at threshold."""
|
|
622
|
+
gray = c.astype(np.float32).mean(axis=2)
|
|
623
|
+
out = np.zeros_like(c); out[gray > thr] = 255
|
|
624
|
+
return out
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
#### Solarize
|
|
628
|
+
```python
|
|
629
|
+
def sh_solarize(c, threshold=128):
|
|
630
|
+
"""Invert pixels above threshold — classic darkroom effect."""
|
|
631
|
+
o = c.copy(); mask = c > threshold; o[mask] = 255 - c[mask]
|
|
632
|
+
return o
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
#### Hue Rotate
|
|
636
|
+
```python
|
|
637
|
+
def sh_hue_rotate(c, amount=0.1):
|
|
638
|
+
"""Rotate all hues by amount (0-1)."""
|
|
639
|
+
h, s, v = rgb2hsv(c[:,:,0], c[:,:,1], c[:,:,2])
|
|
640
|
+
h = (h + amount) % 1.0
|
|
641
|
+
R, G, B = hsv2rgb(h, s, v)
|
|
642
|
+
return mkc(R, G, B, c.shape[0], c.shape[1])
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
#### Saturation
|
|
646
|
+
```python
|
|
647
|
+
def sh_saturation(c, factor=1.5):
|
|
648
|
+
"""Adjust saturation. >1=more saturated, <1=desaturated."""
|
|
649
|
+
h, s, v = rgb2hsv(c[:,:,0], c[:,:,1], c[:,:,2])
|
|
650
|
+
s = np.clip(s * factor, 0, 1)
|
|
651
|
+
R, G, B = hsv2rgb(h, s, v)
|
|
652
|
+
return mkc(R, G, B, c.shape[0], c.shape[1])
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
#### Color Grade
|
|
656
|
+
```python
|
|
657
|
+
def sh_color_grade(c, tint):
|
|
658
|
+
"""Per-channel multiplier. tint=(r_mul, g_mul, b_mul)."""
|
|
659
|
+
o = c.astype(np.float32)
|
|
660
|
+
o[:,:,0] *= tint[0]; o[:,:,1] *= tint[1]; o[:,:,2] *= tint[2]
|
|
661
|
+
return np.clip(o, 0, 255).astype(np.uint8)
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
#### Color Wobble
|
|
665
|
+
```python
|
|
666
|
+
def sh_color_wobble(c, t, amt=0.3):
|
|
667
|
+
"""Time-varying per-channel sine modulation. Audio-reactive in dispatch (amt scaled by rms)."""
|
|
668
|
+
o = c.astype(np.float32)
|
|
669
|
+
o[:,:,0] *= 1.0 + amt * math.sin(t * 5.0)
|
|
670
|
+
o[:,:,1] *= 1.0 + amt * math.sin(t * 5.0 + 2.09)
|
|
671
|
+
o[:,:,2] *= 1.0 + amt * math.sin(t * 5.0 + 4.19)
|
|
672
|
+
return np.clip(o, 0, 255).astype(np.uint8)
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
#### Color Ramp
|
|
676
|
+
```python
|
|
677
|
+
def sh_color_ramp(c, ramp_colors):
|
|
678
|
+
"""Map luminance to a custom color gradient.
|
|
679
|
+
ramp_colors = list of (R,G,B) tuples, evenly spaced from dark to bright."""
|
|
680
|
+
gray = c.astype(np.float32).mean(axis=2) / 255.0
|
|
681
|
+
n = len(ramp_colors)
|
|
682
|
+
idx = np.clip(gray * (n-1), 0, n-1.001)
|
|
683
|
+
lo = np.floor(idx).astype(int); hi = np.minimum(lo+1, n-1)
|
|
684
|
+
frac = idx - lo
|
|
685
|
+
ramp = np.array(ramp_colors, dtype=np.float32)
|
|
686
|
+
out = ramp[lo] * (1-frac[:,:,None]) + ramp[hi] * frac[:,:,None]
|
|
687
|
+
return np.clip(out, 0, 255).astype(np.uint8)
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
### Glow / Blur Shaders
|
|
693
|
+
|
|
694
|
+
#### Bloom
|
|
695
|
+
```python
|
|
696
|
+
def sh_bloom(c, thr=130):
|
|
697
|
+
"""Bright-area glow: 4x downsample, threshold, 3-pass box blur, screen blend."""
|
|
698
|
+
sm = c[::4, ::4].astype(np.float32)
|
|
699
|
+
br = np.where(sm > thr, sm, 0)
|
|
700
|
+
for _ in range(3):
|
|
701
|
+
p = np.pad(br, ((1,1),(1,1),(0,0)), mode="edge")
|
|
702
|
+
br = (p[:-2,:-2]+p[:-2,1:-1]+p[:-2,2:]+p[1:-1,:-2]+p[1:-1,1:-1]+
|
|
703
|
+
p[1:-1,2:]+p[2:,:-2]+p[2:,1:-1]+p[2:,2:]) / 9.0
|
|
704
|
+
bl = np.repeat(np.repeat(br, 4, axis=0), 4, axis=1)[:c.shape[0], :c.shape[1]]
|
|
705
|
+
return np.clip(c.astype(np.float32) + bl * 0.5, 0, 255).astype(np.uint8)
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
#### Edge Glow
|
|
709
|
+
```python
|
|
710
|
+
def sh_edge_glow(c, hue=0.5):
|
|
711
|
+
"""Detect edges via gradient, add colored overlay."""
|
|
712
|
+
gray = c.astype(np.float32).mean(axis=2)
|
|
713
|
+
gx = np.abs(gray[:, 2:] - gray[:, :-2])
|
|
714
|
+
gy = np.abs(gray[2:, :] - gray[:-2, :])
|
|
715
|
+
ex = np.zeros_like(gray); ey = np.zeros_like(gray)
|
|
716
|
+
ex[:, 1:-1] = gx; ey[1:-1, :] = gy
|
|
717
|
+
edge = np.clip((ex + ey) / 255 * 2, 0, 1)
|
|
718
|
+
R, G, B = hsv2rgb(np.full_like(edge, hue), np.full_like(edge, 0.8), edge * 0.5)
|
|
719
|
+
out = c.astype(np.int16).copy()
|
|
720
|
+
out[:,:,0] = np.clip(out[:,:,0] + R.astype(np.int16), 0, 255)
|
|
721
|
+
out[:,:,1] = np.clip(out[:,:,1] + G.astype(np.int16), 0, 255)
|
|
722
|
+
out[:,:,2] = np.clip(out[:,:,2] + B.astype(np.int16), 0, 255)
|
|
723
|
+
return out.astype(np.uint8)
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
#### Soft Focus
|
|
727
|
+
```python
|
|
728
|
+
def sh_soft_focus(c, strength=0.3):
|
|
729
|
+
"""Blend original with 2x-downsampled box blur."""
|
|
730
|
+
sm = c[::2, ::2].astype(np.float32)
|
|
731
|
+
p = np.pad(sm, ((1,1),(1,1),(0,0)), mode="edge")
|
|
732
|
+
bl = (p[:-2,:-2]+p[:-2,1:-1]+p[:-2,2:]+p[1:-1,:-2]+p[1:-1,1:-1]+
|
|
733
|
+
p[1:-1,2:]+p[2:,:-2]+p[2:,1:-1]+p[2:,2:]) / 9.0
|
|
734
|
+
bl = np.repeat(np.repeat(bl, 2, axis=0), 2, axis=1)[:c.shape[0], :c.shape[1]]
|
|
735
|
+
return np.clip(c * (1-strength) + bl * strength, 0, 255).astype(np.uint8)
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
#### Radial Blur
|
|
739
|
+
```python
|
|
740
|
+
def sh_radial_blur(c, strength=0.03, center=None):
|
|
741
|
+
"""Zoom blur from center — motion blur radiating outward."""
|
|
742
|
+
h, w = c.shape[:2]
|
|
743
|
+
cy, cx = center if center else (h//2, w//2)
|
|
744
|
+
Y = np.arange(h, dtype=np.float32)[:, None]
|
|
745
|
+
X = np.arange(w, dtype=np.float32)[None, :]
|
|
746
|
+
out = c.astype(np.float32)
|
|
747
|
+
for s in [strength, strength*2]:
|
|
748
|
+
dy = (Y - cy) * s; dx = (X - cx) * s
|
|
749
|
+
sy = np.clip((Y + dy).astype(int), 0, h-1)
|
|
750
|
+
sx = np.clip((X + dx).astype(int), 0, w-1)
|
|
751
|
+
out += c[sy, sx].astype(np.float32)
|
|
752
|
+
return np.clip(out / 3, 0, 255).astype(np.uint8)
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
### Noise / Grain Shaders
|
|
758
|
+
|
|
759
|
+
#### Film Grain
|
|
760
|
+
```python
|
|
761
|
+
def sh_grain(c, amt=10):
|
|
762
|
+
"""2x-downsampled film grain. Audio-reactive in dispatch (amt scaled by rms)."""
|
|
763
|
+
noise = np.random.randint(-amt, amt+1, ((c.shape[0]+1)//2, (c.shape[1]+1)//2, 1), dtype=np.int16)
|
|
764
|
+
noise = np.repeat(np.repeat(noise, 2, axis=0), 2, axis=1)[:c.shape[0], :c.shape[1]]
|
|
765
|
+
return np.clip(c.astype(np.int16) + noise, 0, 255).astype(np.uint8)
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
#### Static Noise
|
|
769
|
+
```python
|
|
770
|
+
def sh_static_noise(c, density=0.05, color=True):
|
|
771
|
+
"""Random pixel noise overlay (TV static)."""
|
|
772
|
+
mask = np.random.random(((c.shape[0]+1)//2, (c.shape[1]+1)//2)) < density
|
|
773
|
+
mask = np.repeat(np.repeat(mask, 2, axis=0), 2, axis=1)[:c.shape[0], :c.shape[1]]
|
|
774
|
+
out = c.copy()
|
|
775
|
+
if color:
|
|
776
|
+
noise = np.random.randint(0, 256, (c.shape[0], c.shape[1], 3), dtype=np.uint8)
|
|
777
|
+
else:
|
|
778
|
+
v = np.random.randint(0, 256, (c.shape[0], c.shape[1]), dtype=np.uint8)
|
|
779
|
+
noise = np.stack([v, v, v], axis=2)
|
|
780
|
+
out[mask] = noise[mask]
|
|
781
|
+
return out
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
### Lines / Pattern Shaders
|
|
787
|
+
|
|
788
|
+
#### Scanlines
|
|
789
|
+
```python
|
|
790
|
+
def sh_scanlines(c, intensity=0.08, spacing=3):
|
|
791
|
+
"""Darken every Nth row."""
|
|
792
|
+
m = np.ones(c.shape[0], dtype=np.float32)
|
|
793
|
+
m[::spacing] = 1.0 - intensity
|
|
794
|
+
return np.clip(c * m[:, None, None], 0, 255).astype(np.uint8)
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
#### Halftone
|
|
798
|
+
```python
|
|
799
|
+
def sh_halftone(c, dot_size=6):
|
|
800
|
+
"""Halftone dot pattern overlay — circular dots sized by local brightness."""
|
|
801
|
+
h, w = c.shape[:2]
|
|
802
|
+
gray = c.astype(np.float32).mean(axis=2) / 255.0
|
|
803
|
+
out = np.zeros_like(c)
|
|
804
|
+
for y in range(0, h, dot_size):
|
|
805
|
+
for x in range(0, w, dot_size):
|
|
806
|
+
block = gray[y:y+dot_size, x:x+dot_size]
|
|
807
|
+
if block.size == 0: continue
|
|
808
|
+
radius = block.mean() * dot_size * 0.5
|
|
809
|
+
cy_b, cx_b = dot_size//2, dot_size//2
|
|
810
|
+
for dy in range(min(dot_size, h-y)):
|
|
811
|
+
for dx in range(min(dot_size, w-x)):
|
|
812
|
+
if math.sqrt((dy-cy_b)**2 + (dx-cx_b)**2) < radius:
|
|
813
|
+
out[y+dy, x+dx] = c[y+dy, x+dx]
|
|
814
|
+
return out
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
> **Performance note:** Halftone is slow due to Python loops. Acceptable for small resolutions or single test frames. For production, consider a vectorized version using precomputed distance masks.
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
### Tone Shaders
|
|
822
|
+
|
|
823
|
+
#### Vignette
|
|
824
|
+
```python
|
|
825
|
+
_vig_cache = {}
|
|
826
|
+
def sh_vignette(c, s=0.22):
|
|
827
|
+
"""Edge darkening using cached distance field."""
|
|
828
|
+
k = (c.shape[0], c.shape[1], round(s, 2))
|
|
829
|
+
if k not in _vig_cache:
|
|
830
|
+
h, w = c.shape[:2]
|
|
831
|
+
Y = np.linspace(-1, 1, h)[:, None]; X = np.linspace(-1, 1, w)[None, :]
|
|
832
|
+
_vig_cache[k] = np.clip(1.0 - np.sqrt(X**2 + Y**2) * s, 0.15, 1).astype(np.float32)
|
|
833
|
+
return np.clip(c * _vig_cache[k][:,:,None], 0, 255).astype(np.uint8)
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
#### Reverse Vignette
|
|
837
|
+
|
|
838
|
+
Inverted vignette: darkens the **center** and leaves edges bright. Useful when text is centered over busy backgrounds — creates a natural dark zone for readability without a hard-edged box.
|
|
839
|
+
|
|
840
|
+
Combine with `apply_text_backdrop()` (see composition.md) for per-frame glyph-aware darkening.
|
|
841
|
+
|
|
842
|
+
```python
|
|
843
|
+
_rvignette_cache = {}
|
|
844
|
+
|
|
845
|
+
def sh_reverse_vignette(c, strength=0.5):
|
|
846
|
+
"""Center darkening, edge brightening. Cached."""
|
|
847
|
+
k = ('rv', c.shape[0], c.shape[1], round(strength, 2))
|
|
848
|
+
if k not in _rvignette_cache:
|
|
849
|
+
h, w = c.shape[:2]
|
|
850
|
+
Y = np.linspace(-1, 1, h)[:, None]
|
|
851
|
+
X = np.linspace(-1, 1, w)[None, :]
|
|
852
|
+
d = np.sqrt(X**2 + Y**2)
|
|
853
|
+
# Invert: bright at edges, dark at center
|
|
854
|
+
mask = np.clip(1.0 - (1.0 - d * 0.7) * strength, 0.2, 1.0)
|
|
855
|
+
_rvignette_cache[k] = mask[:, :, np.newaxis].astype(np.float32)
|
|
856
|
+
return np.clip(c.astype(np.float32) * _rvignette_cache[k], 0, 255).astype(np.uint8)
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
| Param | Default | Effect |
|
|
860
|
+
|-------|---------|--------|
|
|
861
|
+
| `strength` | 0.5 | 0 = no effect, 1.0 = center nearly black |
|
|
862
|
+
|
|
863
|
+
Add to ShaderChain dispatch:
|
|
864
|
+
```python
|
|
865
|
+
elif name == "reverse_vignette":
|
|
866
|
+
return sh_reverse_vignette(canvas, kwargs.get("strength", 0.5))
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
#### Contrast
|
|
870
|
+
```python
|
|
871
|
+
def sh_contrast(c, factor=1.3):
|
|
872
|
+
"""Adjust contrast around midpoint 128."""
|
|
873
|
+
return np.clip((c.astype(np.float32) - 128) * factor + 128, 0, 255).astype(np.uint8)
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
#### Gamma
|
|
877
|
+
```python
|
|
878
|
+
def sh_gamma(c, gamma=1.5):
|
|
879
|
+
"""Gamma correction. >1=brighter mids, <1=darker mids."""
|
|
880
|
+
return np.clip(((c.astype(np.float32)/255.0) ** (1.0/gamma)) * 255, 0, 255).astype(np.uint8)
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
#### Levels
|
|
884
|
+
```python
|
|
885
|
+
def sh_levels(c, black=0, white=255, midtone=1.0):
|
|
886
|
+
"""Levels adjustment (Photoshop-style). Remap black/white points, apply midtone gamma."""
|
|
887
|
+
o = (c.astype(np.float32) - black) / max(1, white - black)
|
|
888
|
+
o = np.clip(o, 0, 1) ** (1.0 / midtone)
|
|
889
|
+
return (o * 255).astype(np.uint8)
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
#### Brightness
|
|
893
|
+
```python
|
|
894
|
+
def sh_brightness(c, factor=1.5):
|
|
895
|
+
"""Global brightness multiplier. Prefer tonemap() for scene-level brightness control."""
|
|
896
|
+
return np.clip(c.astype(np.float32) * factor, 0, 255).astype(np.uint8)
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
---
|
|
900
|
+
|
|
901
|
+
### Glitch / Data Shaders
|
|
902
|
+
|
|
903
|
+
#### Glitch Bands
|
|
904
|
+
```python
|
|
905
|
+
def sh_glitch_bands(c, f):
|
|
906
|
+
"""Beat-reactive horizontal row displacement. f = audio features dict.
|
|
907
|
+
Uses f["bdecay"] for intensity and f["sub"] for band height."""
|
|
908
|
+
n = int(3 + f.get("bdecay", 0) * 10)
|
|
909
|
+
out = c.copy()
|
|
910
|
+
for _ in range(n):
|
|
911
|
+
y = random.randint(0, c.shape[0]-1)
|
|
912
|
+
h = random.randint(1, max(2, int(4 + f.get("sub", 0.3) * 12)))
|
|
913
|
+
shift = int((random.random()-0.5) * f.get("bdecay", 0) * 60)
|
|
914
|
+
if shift != 0 and y+h < c.shape[0]:
|
|
915
|
+
out[y:y+h] = np.roll(out[y:y+h], shift, axis=1)
|
|
916
|
+
return out
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
#### Block Glitch
|
|
920
|
+
```python
|
|
921
|
+
def sh_block_glitch(c, n_blocks=8, max_size=40):
|
|
922
|
+
"""Random rectangular block displacement — copy blocks to random positions."""
|
|
923
|
+
out = c.copy(); h, w = c.shape[:2]
|
|
924
|
+
for _ in range(n_blocks):
|
|
925
|
+
bw = random.randint(10, max_size); bh = random.randint(5, max_size//2)
|
|
926
|
+
sx = random.randint(0, w-bw-1); sy = random.randint(0, h-bh-1)
|
|
927
|
+
dx = random.randint(0, w-bw-1); dy = random.randint(0, h-bh-1)
|
|
928
|
+
out[dy:dy+bh, dx:dx+bw] = c[sy:sy+bh, sx:sx+bw]
|
|
929
|
+
return out
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
#### Pixel Sort
|
|
933
|
+
```python
|
|
934
|
+
def sh_pixel_sort(c, threshold=100, direction="h"):
|
|
935
|
+
"""Sort pixels by brightness in contiguous bright regions."""
|
|
936
|
+
gray = c.astype(np.float32).mean(axis=2)
|
|
937
|
+
out = c.copy()
|
|
938
|
+
if direction == "h":
|
|
939
|
+
for y in range(0, c.shape[0], 3): # every 3rd row for speed
|
|
940
|
+
row_bright = gray[y]
|
|
941
|
+
mask = row_bright > threshold
|
|
942
|
+
regions = np.diff(np.concatenate([[0], mask.astype(int), [0]]))
|
|
943
|
+
starts = np.where(regions == 1)[0]
|
|
944
|
+
ends = np.where(regions == -1)[0]
|
|
945
|
+
for s, e in zip(starts, ends):
|
|
946
|
+
if e - s > 2:
|
|
947
|
+
indices = np.argsort(gray[y, s:e])
|
|
948
|
+
out[y, s:e] = c[y, s:e][indices]
|
|
949
|
+
else:
|
|
950
|
+
for x in range(0, c.shape[1], 3):
|
|
951
|
+
col_bright = gray[:, x]
|
|
952
|
+
mask = col_bright > threshold
|
|
953
|
+
regions = np.diff(np.concatenate([[0], mask.astype(int), [0]]))
|
|
954
|
+
starts = np.where(regions == 1)[0]
|
|
955
|
+
ends = np.where(regions == -1)[0]
|
|
956
|
+
for s, e in zip(starts, ends):
|
|
957
|
+
if e - s > 2:
|
|
958
|
+
indices = np.argsort(gray[s:e, x])
|
|
959
|
+
out[s:e, x] = c[s:e, x][indices]
|
|
960
|
+
return out
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
#### Data Bend
|
|
964
|
+
```python
|
|
965
|
+
def sh_data_bend(c, offset=1000, chunk=500):
|
|
966
|
+
"""Treat raw pixel bytes as data, copy a chunk to another offset — datamosh artifacts."""
|
|
967
|
+
flat = c.flatten().copy()
|
|
968
|
+
n = len(flat)
|
|
969
|
+
src = offset % n; dst = (offset + chunk*3) % n
|
|
970
|
+
length = min(chunk, n-src, n-dst)
|
|
971
|
+
if length > 0:
|
|
972
|
+
flat[dst:dst+length] = flat[src:src+length]
|
|
973
|
+
return flat.reshape(c.shape)
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
---
|
|
977
|
+
|
|
978
|
+
## Tint Presets
|
|
979
|
+
|
|
980
|
+
```python
|
|
981
|
+
TINT_WARM = (1.15, 1.0, 0.85) # golden warmth
|
|
982
|
+
TINT_COOL = (0.85, 0.95, 1.15) # blue cool
|
|
983
|
+
TINT_MATRIX = (0.7, 1.2, 0.7) # green terminal
|
|
984
|
+
TINT_AMBER = (1.2, 0.9, 0.6) # amber monitor
|
|
985
|
+
TINT_SEPIA = (1.2, 1.05, 0.8) # old film
|
|
986
|
+
TINT_NEON_PINK = (1.3, 0.7, 1.1) # cyberpunk pink
|
|
987
|
+
TINT_ICE = (0.8, 1.0, 1.3) # frozen
|
|
988
|
+
TINT_BLOOD = (1.4, 0.7, 0.7) # horror red
|
|
989
|
+
TINT_FOREST = (0.8, 1.15, 0.75) # natural green
|
|
990
|
+
TINT_VOID = (0.85, 0.85, 1.1) # deep space
|
|
991
|
+
TINT_SUNSET = (1.3, 0.85, 0.7) # orange dusk
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
---
|
|
995
|
+
|
|
996
|
+
## Transitions
|
|
997
|
+
|
|
998
|
+
> **Note:** These operate on character-level `(chars, colors)` arrays (v1 interface). In v2, transitions between scenes are typically handled by hard cuts at beat boundaries (see `scenes.md`), or by rendering both scenes to canvases and using `blend_canvas()` with a time-varying opacity. The character-level transitions below are still useful for within-scene effects.
|
|
999
|
+
|
|
1000
|
+
### Crossfade
|
|
1001
|
+
```python
|
|
1002
|
+
def tr_crossfade(ch_a, co_a, ch_b, co_b, blend):
|
|
1003
|
+
co = (co_a.astype(np.float32) * (1-blend) + co_b.astype(np.float32) * blend).astype(np.uint8)
|
|
1004
|
+
mask = np.random.random(ch_a.shape) < blend
|
|
1005
|
+
ch = ch_a.copy(); ch[mask] = ch_b[mask]
|
|
1006
|
+
return ch, co
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
### v2 Canvas-Level Crossfade
|
|
1010
|
+
```python
|
|
1011
|
+
def tr_canvas_crossfade(canvas_a, canvas_b, blend):
|
|
1012
|
+
"""Smooth pixel crossfade between two canvases."""
|
|
1013
|
+
return np.clip(canvas_a * (1-blend) + canvas_b * blend, 0, 255).astype(np.uint8)
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### Wipe (directional)
|
|
1017
|
+
```python
|
|
1018
|
+
def tr_wipe(ch_a, co_a, ch_b, co_b, blend, direction="left"):
|
|
1019
|
+
"""direction: left, right, up, down, radial, diagonal"""
|
|
1020
|
+
rows, cols = ch_a.shape
|
|
1021
|
+
blend = float(np.clip(blend, 0, 1))
|
|
1022
|
+
rr = np.arange(rows)[:, None]; cc = np.arange(cols)[None, :]
|
|
1023
|
+
if direction == "radial":
|
|
1024
|
+
cx, cy = cols/2, rows/2
|
|
1025
|
+
rr = np.arange(rows)[:, None]; cc = np.arange(cols)[None, :]
|
|
1026
|
+
d = np.sqrt((cc-cx)**2 + (rr-cy)**2)
|
|
1027
|
+
mask = d < blend * np.sqrt(cx**2 + cy**2)
|
|
1028
|
+
elif direction == "left":
|
|
1029
|
+
mask = np.broadcast_to(cc < blend * cols, (rows, cols))
|
|
1030
|
+
elif direction == "right":
|
|
1031
|
+
mask = np.broadcast_to(cc >= (1 - blend) * cols, (rows, cols))
|
|
1032
|
+
elif direction == "up":
|
|
1033
|
+
mask = np.broadcast_to(rr < blend * rows, (rows, cols))
|
|
1034
|
+
elif direction == "down":
|
|
1035
|
+
mask = np.broadcast_to(rr >= (1 - blend) * rows, (rows, cols))
|
|
1036
|
+
elif direction == "diagonal":
|
|
1037
|
+
mask = (rr + cc) < blend * (rows + cols)
|
|
1038
|
+
else:
|
|
1039
|
+
raise ValueError("Unknown wipe direction")
|
|
1040
|
+
if blend == 0: mask = np.zeros((rows, cols), dtype=bool)
|
|
1041
|
+
if blend == 1: mask = np.ones((rows, cols), dtype=bool)
|
|
1042
|
+
ch = ch_a.copy(); co = co_a.copy()
|
|
1043
|
+
ch[mask] = ch_b[mask]; co[mask] = co_b[mask]
|
|
1044
|
+
return ch, co
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
### Glitch Cut
|
|
1048
|
+
```python
|
|
1049
|
+
def tr_glitch_cut(ch_a, co_a, ch_b, co_b, blend):
|
|
1050
|
+
if blend < 0.5: ch, co = ch_a.copy(), co_a.copy()
|
|
1051
|
+
else: ch, co = ch_b.copy(), co_b.copy()
|
|
1052
|
+
if 0.3 < blend < 0.7:
|
|
1053
|
+
intensity = 1.0 - abs(blend - 0.5) * 4
|
|
1054
|
+
for _ in range(int(intensity * 20)):
|
|
1055
|
+
y = random.randint(0, ch.shape[0]-1)
|
|
1056
|
+
shift = int((random.random()-0.5) * 40 * intensity)
|
|
1057
|
+
if shift: ch[y] = np.roll(ch[y], shift); co[y] = np.roll(co[y], shift, axis=0)
|
|
1058
|
+
return ch, co
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
---
|
|
1062
|
+
|
|
1063
|
+
## Output Formats
|
|
1064
|
+
|
|
1065
|
+
### MP4 (default)
|
|
1066
|
+
```python
|
|
1067
|
+
cmd = ["ffmpeg", "-y", "-f", "rawvideo", "-pix_fmt", "rgb24",
|
|
1068
|
+
"-s", f"{W}x{H}", "-r", str(fps), "-i", "pipe:0",
|
|
1069
|
+
"-c:v", "libx264", "-preset", "fast", "-crf", str(crf),
|
|
1070
|
+
"-pix_fmt", "yuv420p", output_path]
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
### GIF
|
|
1074
|
+
```python
|
|
1075
|
+
cmd = ["ffmpeg", "-y", "-f", "rawvideo", "-pix_fmt", "rgb24",
|
|
1076
|
+
"-s", f"{W}x{H}", "-r", str(fps), "-i", "pipe:0",
|
|
1077
|
+
"-vf", f"fps={fps},scale={W}:{H}:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse",
|
|
1078
|
+
"-loop", "0", output_gif]
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
### PNG Sequence
|
|
1082
|
+
|
|
1083
|
+
For frame-accurate editing, compositing in external tools (After Effects, Nuke), or lossless archival:
|
|
1084
|
+
|
|
1085
|
+
```python
|
|
1086
|
+
import os
|
|
1087
|
+
|
|
1088
|
+
def output_png_sequence(frames, output_dir, W, H, fps, prefix="frame"):
|
|
1089
|
+
"""Write frames as numbered PNGs. frames = iterable of uint8 (H,W,3) arrays."""
|
|
1090
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
1091
|
+
|
|
1092
|
+
# Method 1: Direct PIL write (no ffmpeg dependency)
|
|
1093
|
+
from PIL import Image
|
|
1094
|
+
for i, frame in enumerate(frames):
|
|
1095
|
+
img = Image.fromarray(frame)
|
|
1096
|
+
img.save(os.path.join(output_dir, f"{prefix}_{i:06d}.png"))
|
|
1097
|
+
|
|
1098
|
+
# Method 2: ffmpeg pipe (faster for large sequences)
|
|
1099
|
+
cmd = ["ffmpeg", "-y", "-f", "rawvideo", "-pix_fmt", "rgb24",
|
|
1100
|
+
"-s", f"{W}x{H}", "-r", str(fps), "-i", "pipe:0",
|
|
1101
|
+
os.path.join(output_dir, f"{prefix}_%06d.png")]
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
Reassemble PNG sequence to video:
|
|
1105
|
+
```bash
|
|
1106
|
+
ffmpeg -framerate 24 -i frame_%06d.png -c:v libx264 -crf 18 -pix_fmt yuv420p output.mp4
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
### Alpha Channel / Transparent Background (RGBA)
|
|
1110
|
+
|
|
1111
|
+
For compositing ASCII art over other video or images. Uses RGBA canvas (4 channels) instead of RGB (3 channels):
|
|
1112
|
+
|
|
1113
|
+
```python
|
|
1114
|
+
def create_rgba_canvas(H, W):
|
|
1115
|
+
"""Transparent canvas — alpha channel starts at 0 (fully transparent)."""
|
|
1116
|
+
return np.zeros((H, W, 4), dtype=np.uint8)
|
|
1117
|
+
|
|
1118
|
+
def render_char_rgba(canvas, row, col, char_img, color_rgb, alpha=255):
|
|
1119
|
+
"""Render a character with alpha. char_img = PIL glyph mask (grayscale).
|
|
1120
|
+
Alpha comes from the glyph mask — background stays transparent."""
|
|
1121
|
+
# Straight-alpha source-over; convert before multiplication to avoid uint8 overflow.
|
|
1122
|
+
mask = np.asarray(char_img, dtype=np.float32) / 255.0
|
|
1123
|
+
height, width = mask.shape
|
|
1124
|
+
y0, x0 = row * height, col * width
|
|
1125
|
+
if y0 < 0 or x0 < 0 or y0 + height > canvas.shape[0] or x0 + width > canvas.shape[1]:
|
|
1126
|
+
raise ValueError("Glyph is outside the canvas")
|
|
1127
|
+
tile = canvas[y0:y0+height, x0:x0+width]
|
|
1128
|
+
src_a = mask[..., None] * np.clip(alpha / 255.0, 0, 1)
|
|
1129
|
+
dst_a = tile[..., 3:4].astype(np.float32) / 255.0
|
|
1130
|
+
out_a = src_a + dst_a * (1 - src_a)
|
|
1131
|
+
rgb = (np.asarray(color_rgb, dtype=np.float32) * src_a
|
|
1132
|
+
+ tile[..., :3].astype(np.float32) * dst_a * (1 - src_a))
|
|
1133
|
+
rgb = np.divide(rgb, out_a, out=np.zeros_like(rgb), where=out_a > 0)
|
|
1134
|
+
tile[..., :3] = np.clip(np.rint(rgb), 0, 255).astype(np.uint8)
|
|
1135
|
+
tile[..., 3:4] = np.clip(np.rint(out_a * 255), 0, 255).astype(np.uint8)
|
|
1136
|
+
|
|
1137
|
+
def blend_onto_background(rgba_canvas, bg_rgb):
|
|
1138
|
+
"""Composite RGBA canvas over a solid or image background."""
|
|
1139
|
+
alpha = rgba_canvas[:, :, 3:4].astype(np.float32) / 255.0
|
|
1140
|
+
fg = rgba_canvas[:, :, :3].astype(np.float32)
|
|
1141
|
+
bg = bg_rgb.astype(np.float32)
|
|
1142
|
+
result = fg * alpha + bg * (1.0 - alpha)
|
|
1143
|
+
return result.astype(np.uint8)
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
RGBA output via ffmpeg (ProRes 4444 for editing, WebM VP9 for web):
|
|
1147
|
+
```bash
|
|
1148
|
+
# ProRes 4444 — preserves alpha, widely supported in NLEs
|
|
1149
|
+
ffmpeg -y -f rawvideo -pix_fmt rgba -s {W}x{H} -r {fps} -i pipe:0 \
|
|
1150
|
+
-c:v prores_ks -profile:v 4444 -pix_fmt yuva444p10le output.mov
|
|
1151
|
+
|
|
1152
|
+
# WebM VP9 — alpha support for web/browser compositing
|
|
1153
|
+
ffmpeg -y -f rawvideo -pix_fmt rgba -s {W}x{H} -r {fps} -i pipe:0 \
|
|
1154
|
+
-c:v libvpx-vp9 -pix_fmt yuva420p -crf 30 -b:v 0 output.webm
|
|
1155
|
+
|
|
1156
|
+
# PNG sequence with alpha (lossless)
|
|
1157
|
+
ffmpeg -y -f rawvideo -pix_fmt rgba -s {W}x{H} -r {fps} -i pipe:0 \
|
|
1158
|
+
frame_%06d.png
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
**Key constraint**: shaders that operate on `(H,W,3)` arrays need adaptation for RGBA. Color-only transforms may preserve alpha; geometric transforms must apply the same coordinate mapping to all four channels. The following wrapper is only for color-only transforms:
|
|
1162
|
+
|
|
1163
|
+
```python
|
|
1164
|
+
def apply_shader_rgba(canvas_rgba, shader_fn, **kwargs):
|
|
1165
|
+
"""Apply an RGB shader to the color channels of an RGBA canvas."""
|
|
1166
|
+
rgb = canvas_rgba[:, :, :3]
|
|
1167
|
+
alpha = canvas_rgba[:, :, 3:4]
|
|
1168
|
+
rgb_out = shader_fn(rgb, **kwargs)
|
|
1169
|
+
return np.concatenate([rgb_out, alpha], axis=2)
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
---
|
|
1173
|
+
|
|
1174
|
+
## Real-Time Terminal Rendering
|
|
1175
|
+
|
|
1176
|
+
Live ASCII display in the terminal using ANSI escape codes. Useful for previewing scenes during development, live performances, and interactive parameter tuning.
|
|
1177
|
+
|
|
1178
|
+
### ANSI Color Escape Codes
|
|
1179
|
+
|
|
1180
|
+
```python
|
|
1181
|
+
def rgb_to_ansi(r, g, b):
|
|
1182
|
+
"""24-bit true color ANSI escape (supported by most modern terminals)."""
|
|
1183
|
+
return f"\033[38;2;{r};{g};{b}m"
|
|
1184
|
+
|
|
1185
|
+
ANSI_RESET = "\033[0m"
|
|
1186
|
+
ANSI_CLEAR = "\033[2J\033[H" # clear screen + cursor home
|
|
1187
|
+
ANSI_HIDE_CURSOR = "\033[?25l"
|
|
1188
|
+
ANSI_SHOW_CURSOR = "\033[?25h"
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
### Frame-to-ANSI Conversion
|
|
1192
|
+
|
|
1193
|
+
```python
|
|
1194
|
+
def frame_to_ansi(chars, colors):
|
|
1195
|
+
"""Convert char+color arrays to a single ANSI string for terminal output.
|
|
1196
|
+
|
|
1197
|
+
Args:
|
|
1198
|
+
chars: (rows, cols) array of single characters
|
|
1199
|
+
colors: (rows, cols, 3) uint8 RGB array
|
|
1200
|
+
Returns:
|
|
1201
|
+
str: ANSI-encoded frame ready for sys.stdout.write()
|
|
1202
|
+
"""
|
|
1203
|
+
rows, cols = chars.shape
|
|
1204
|
+
lines = []
|
|
1205
|
+
for r in range(rows):
|
|
1206
|
+
parts = []
|
|
1207
|
+
prev_color = None
|
|
1208
|
+
for c in range(cols):
|
|
1209
|
+
rgb = tuple(colors[r, c])
|
|
1210
|
+
ch = chars[r, c]
|
|
1211
|
+
if ch == " " or rgb == (0, 0, 0):
|
|
1212
|
+
parts.append(" ")
|
|
1213
|
+
else:
|
|
1214
|
+
if rgb != prev_color:
|
|
1215
|
+
parts.append(rgb_to_ansi(*rgb))
|
|
1216
|
+
prev_color = rgb
|
|
1217
|
+
parts.append(ch)
|
|
1218
|
+
parts.append(ANSI_RESET)
|
|
1219
|
+
lines.append("".join(parts))
|
|
1220
|
+
return "\n".join(lines)
|
|
1221
|
+
```
|
|
1222
|
+
|
|
1223
|
+
### Optimized: Delta Updates
|
|
1224
|
+
|
|
1225
|
+
Only redraw characters that changed since the last frame. Eliminates redundant terminal writes for static regions:
|
|
1226
|
+
|
|
1227
|
+
```python
|
|
1228
|
+
def frame_to_ansi_delta(chars, colors, prev_chars, prev_colors):
|
|
1229
|
+
"""Emit ANSI escapes only for cells that changed."""
|
|
1230
|
+
rows, cols = chars.shape
|
|
1231
|
+
parts = []
|
|
1232
|
+
for r in range(rows):
|
|
1233
|
+
for c in range(cols):
|
|
1234
|
+
if (chars[r, c] != prev_chars[r, c] or
|
|
1235
|
+
not np.array_equal(colors[r, c], prev_colors[r, c])):
|
|
1236
|
+
parts.append(f"\033[{r+1};{c+1}H") # move cursor
|
|
1237
|
+
rgb = tuple(colors[r, c])
|
|
1238
|
+
parts.append(rgb_to_ansi(*rgb))
|
|
1239
|
+
parts.append(chars[r, c])
|
|
1240
|
+
return "".join(parts)
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
### Live Render Loop
|
|
1244
|
+
|
|
1245
|
+
```python
|
|
1246
|
+
import sys
|
|
1247
|
+
import time
|
|
1248
|
+
|
|
1249
|
+
def render_live(scene_fn, r, fps=24, duration=None):
|
|
1250
|
+
"""Render a scene function live in the terminal.
|
|
1251
|
+
|
|
1252
|
+
Args:
|
|
1253
|
+
scene_fn: v2 scene function (r, f, t, S) -> canvas
|
|
1254
|
+
OR v1-style function that populates a grid
|
|
1255
|
+
r: Renderer instance
|
|
1256
|
+
fps: target frame rate
|
|
1257
|
+
duration: seconds to run (None = run until Ctrl+C)
|
|
1258
|
+
"""
|
|
1259
|
+
frame_time = 1.0 / fps
|
|
1260
|
+
S = {}
|
|
1261
|
+
f = {} # synthesize features or connect to live audio
|
|
1262
|
+
|
|
1263
|
+
sys.stdout.write(ANSI_HIDE_CURSOR + ANSI_CLEAR)
|
|
1264
|
+
sys.stdout.flush()
|
|
1265
|
+
|
|
1266
|
+
t0 = time.monotonic()
|
|
1267
|
+
frame_count = 0
|
|
1268
|
+
try:
|
|
1269
|
+
while True:
|
|
1270
|
+
t = time.monotonic() - t0
|
|
1271
|
+
if duration and t > duration:
|
|
1272
|
+
break
|
|
1273
|
+
|
|
1274
|
+
# Synthesize features from time (or connect to live audio via pyaudio)
|
|
1275
|
+
f = synthesize_features(t)
|
|
1276
|
+
|
|
1277
|
+
# Render scene — for terminal, use a small grid
|
|
1278
|
+
g = r.get_grid("sm")
|
|
1279
|
+
# Option A: v2 scene → extract chars/colors from canvas (reverse render)
|
|
1280
|
+
# Option B: call effect functions directly for chars/colors
|
|
1281
|
+
canvas = scene_fn(r, f, t, S)
|
|
1282
|
+
|
|
1283
|
+
# For terminal display, render chars+colors directly
|
|
1284
|
+
# (bypassing the pixel canvas — terminal uses character cells)
|
|
1285
|
+
chars, colors = scene_to_terminal(scene_fn, r, f, t, S, g)
|
|
1286
|
+
|
|
1287
|
+
frame_str = ANSI_CLEAR + frame_to_ansi(chars, colors)
|
|
1288
|
+
sys.stdout.write(frame_str)
|
|
1289
|
+
sys.stdout.flush()
|
|
1290
|
+
|
|
1291
|
+
# Frame timing
|
|
1292
|
+
elapsed = time.monotonic() - t0 - (frame_count * frame_time)
|
|
1293
|
+
sleep_time = frame_time - elapsed
|
|
1294
|
+
if sleep_time > 0:
|
|
1295
|
+
time.sleep(sleep_time)
|
|
1296
|
+
frame_count += 1
|
|
1297
|
+
except KeyboardInterrupt:
|
|
1298
|
+
pass
|
|
1299
|
+
finally:
|
|
1300
|
+
sys.stdout.write(ANSI_SHOW_CURSOR + ANSI_RESET + "\n")
|
|
1301
|
+
sys.stdout.flush()
|
|
1302
|
+
|
|
1303
|
+
def scene_to_terminal(scene_fn, r, f, t, S, g):
|
|
1304
|
+
"""Run effect functions and return (chars, colors) for terminal display.
|
|
1305
|
+
For terminal mode, skip the pixel canvas and work with character arrays directly."""
|
|
1306
|
+
# Effects that return (chars, colors) work directly
|
|
1307
|
+
# For vf-based effects, render the value field + hue field to chars/colors:
|
|
1308
|
+
val = vf_plasma(g, f, t, S)
|
|
1309
|
+
hue = hf_time_cycle(0.08)(g, f, t, S)
|
|
1310
|
+
mask = val > 0.03
|
|
1311
|
+
chars = val2char(val, mask, PAL_DENSE)
|
|
1312
|
+
R, G, B = hsv2rgb(hue, np.full_like(val, 0.8), val)
|
|
1313
|
+
colors = mkc(R, G, B, g.rows, g.cols)
|
|
1314
|
+
return chars, colors
|
|
1315
|
+
```
|
|
1316
|
+
|
|
1317
|
+
### Curses-Based Rendering (More Robust)
|
|
1318
|
+
|
|
1319
|
+
For full-featured terminal UIs with proper resize handling and input:
|
|
1320
|
+
|
|
1321
|
+
```python
|
|
1322
|
+
import curses
|
|
1323
|
+
|
|
1324
|
+
def render_curses(scene_fn, r, fps=24):
|
|
1325
|
+
"""Curses-based live renderer with resize handling and key input."""
|
|
1326
|
+
|
|
1327
|
+
def _main(stdscr):
|
|
1328
|
+
curses.start_color()
|
|
1329
|
+
curses.use_default_colors()
|
|
1330
|
+
curses.curs_set(0) # hide cursor
|
|
1331
|
+
stdscr.nodelay(True) # non-blocking input
|
|
1332
|
+
|
|
1333
|
+
# Initialize color pairs (curses supports 256 colors)
|
|
1334
|
+
# Map RGB to nearest curses color pair
|
|
1335
|
+
color_cache = {}
|
|
1336
|
+
next_pair = [1]
|
|
1337
|
+
|
|
1338
|
+
def get_color_pair(r, g, b):
|
|
1339
|
+
key = (r >> 4, g >> 4, b >> 4) # quantize to reduce pairs
|
|
1340
|
+
if key not in color_cache:
|
|
1341
|
+
if next_pair[0] < curses.COLOR_PAIRS - 1:
|
|
1342
|
+
ci = 16 + (r // 51) * 36 + (g // 51) * 6 + (b // 51) # 6x6x6 cube
|
|
1343
|
+
curses.init_pair(next_pair[0], ci, -1)
|
|
1344
|
+
color_cache[key] = next_pair[0]
|
|
1345
|
+
next_pair[0] += 1
|
|
1346
|
+
else:
|
|
1347
|
+
return 0
|
|
1348
|
+
return curses.color_pair(color_cache[key])
|
|
1349
|
+
|
|
1350
|
+
S = {}
|
|
1351
|
+
f = {}
|
|
1352
|
+
frame_time = 1.0 / fps
|
|
1353
|
+
t0 = time.monotonic()
|
|
1354
|
+
|
|
1355
|
+
while True:
|
|
1356
|
+
t = time.monotonic() - t0
|
|
1357
|
+
f = synthesize_features(t)
|
|
1358
|
+
|
|
1359
|
+
# Adapt grid to terminal size
|
|
1360
|
+
max_y, max_x = stdscr.getmaxyx()
|
|
1361
|
+
g = r.get_grid_for_size(max_x, max_y) # dynamic grid sizing
|
|
1362
|
+
|
|
1363
|
+
chars, colors = scene_to_terminal(scene_fn, r, f, t, S, g)
|
|
1364
|
+
rows, cols = chars.shape
|
|
1365
|
+
|
|
1366
|
+
for row in range(min(rows, max_y - 1)):
|
|
1367
|
+
for col in range(min(cols, max_x - 1)):
|
|
1368
|
+
ch = chars[row, col]
|
|
1369
|
+
rgb = tuple(colors[row, col])
|
|
1370
|
+
try:
|
|
1371
|
+
stdscr.addch(row, col, ch, get_color_pair(*rgb))
|
|
1372
|
+
except curses.error:
|
|
1373
|
+
pass # ignore writes outside terminal bounds
|
|
1374
|
+
|
|
1375
|
+
stdscr.refresh()
|
|
1376
|
+
|
|
1377
|
+
# Handle input
|
|
1378
|
+
key = stdscr.getch()
|
|
1379
|
+
if key == ord('q'):
|
|
1380
|
+
break
|
|
1381
|
+
|
|
1382
|
+
time.sleep(max(0, frame_time - (time.monotonic() - t0 - t)))
|
|
1383
|
+
|
|
1384
|
+
curses.wrapper(_main)
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
### Terminal Rendering Constraints
|
|
1388
|
+
|
|
1389
|
+
| Constraint | Value | Notes |
|
|
1390
|
+
|-----------|-------|-------|
|
|
1391
|
+
| Max practical grid | ~200x60 | Depends on terminal size |
|
|
1392
|
+
| Color support | 24-bit (modern), 256 (fallback), 16 (minimal) | Check `$COLORTERM` for truecolor |
|
|
1393
|
+
| Frame rate ceiling | ~30 fps | Terminal I/O is the bottleneck |
|
|
1394
|
+
| Delta updates | 2-5x faster | Only worth it when <30% of cells change per frame |
|
|
1395
|
+
| SSH latency | Kills performance | Local terminals only for real-time |
|
|
1396
|
+
|
|
1397
|
+
**Detect color support:**
|
|
1398
|
+
```python
|
|
1399
|
+
import os
|
|
1400
|
+
def get_terminal_color_depth():
|
|
1401
|
+
ct = os.environ.get("COLORTERM", "")
|
|
1402
|
+
if ct in ("truecolor", "24bit"):
|
|
1403
|
+
return 24
|
|
1404
|
+
term = os.environ.get("TERM", "")
|
|
1405
|
+
if "256color" in term:
|
|
1406
|
+
return 8 # 256 colors
|
|
1407
|
+
return 4 # 16 colors basic ANSI
|
|
1408
|
+
```
|