From 6c8f4e756e9fb8f62132ca4c4d55c0cbf7497401 Mon Sep 17 00:00:00 2001 From: axif Date: Sat, 21 Feb 2026 03:37:50 +0600 Subject: [PATCH 1/2] feat: Add Superposition brush effect that generates quantum-inspired probabilistic ghost strokes using Qiskit. --- effect/superposition/README.md | 20 ++++ effect/superposition/__init__.py | 0 effect/superposition/superposition.py | 92 +++++++++++++++++++ .../superposition_requirements.json | 44 +++++++++ 4 files changed, 156 insertions(+) create mode 100644 effect/superposition/README.md create mode 100644 effect/superposition/__init__.py create mode 100644 effect/superposition/superposition.py create mode 100644 effect/superposition/superposition_requirements.json diff --git a/effect/superposition/README.md b/effect/superposition/README.md new file mode 100644 index 0000000..c8e239e --- /dev/null +++ b/effect/superposition/README.md @@ -0,0 +1,20 @@ +# Superposition Brush + +## Background and Motivation +In quantum mechanics, a system can exist in a linear combination of multiple distinct states simultaneously. This phenomenon is known as **Quantum Superposition**. A famous macroscopic illustration of this is Schrödinger's cat, which is both alive and dead until an observation collapses the wavefunction. + +The *Superposition Brush* seeks to capture this fundamental quantum characteristic visually in the context of digital painting. When you draw a stroke, instead of a definitive, single path, the stroke is probabilistically dispersed into multiple potential paths. + +## How it works +Under the hood, this brush utilizes an `AerSimulator` from `qiskit_aer` to evaluate a pure superposition quantum circuit constructed using Hadamard (`H`) gates. By measuring this quantum state into classical bitstrings, the algorithm maps the measurements to a physical 2D scatter pattern around the primary stroke, simulating positional uncertainty. + +The brush renders a primary solid stroke and a number of 'ghost' strokes. The offsets of these 'ghost' segments actively demonstrate the resulting probability distribution. + +## Screenshots +*(Provide your own screenshots showcasing the superposition ghost strokes trailing your main path!)* + +## Usage +- **Radius**: Modifies the base width of your stroke and the proportional size of your ghost strokes. +- **Strength**: Increases the opacity of the main color and the probabilistic ghosts. +- **Spread**: Represents the variance (analogous to the width of the wave packet). A wider spread will push the superposition ghosts further apart. +- **Color**: Select the fundamental color eigenvalue for this stroke. diff --git a/effect/superposition/__init__.py b/effect/superposition/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/effect/superposition/superposition.py b/effect/superposition/superposition.py new file mode 100644 index 0000000..183a318 --- /dev/null +++ b/effect/superposition/superposition.py @@ -0,0 +1,92 @@ +import numpy as np +from qiskit import QuantumCircuit, transpile +from qiskit_aer import AerSimulator +import importlib.util +import os + +spec = importlib.util.spec_from_file_location("utils", "effect/utils.py") +utils = importlib.util.module_from_spec(spec) +spec.loader.exec_module(utils) + +def get_quantum_offsets(num_points, max_spread, num_qubits=4): + offsets = [] + simulator = AerSimulator() + + qc = QuantumCircuit(num_qubits, num_qubits) + for i in range(num_qubits): + qc.h(i) + # add measurements + qc.measure(list(range(num_qubits)), list(range(num_qubits))) + + compiled_circuit = transpile(qc, simulator) + + max_val = (2 ** num_qubits) - 1 + shots = max(1, num_points * 2) + job = simulator.run(compiled_circuit, shots=shots, memory=True) + result = job.result() + memory = result.get_memory() + + for i in range(num_points): + val_x = int(memory[2*i], 2) + val_y = int(memory[2*i + 1], 2) + + off_x = (val_x / max_val) * 2 * max_spread - max_spread + off_y = (val_y / max_val) * 2 * max_spread - max_spread + offsets.append((off_x, off_y)) + + return offsets + +def run(params): + image = params["stroke_input"]["image_rgba"].copy() + height, width = image.shape[:2] + path = params["stroke_input"]["path"] + + radius = params["user_input"]["Radius"] + strength = params["user_input"]["Strength"] + spread = params["user_input"]["Spread"] + color = params["user_input"]["Color"] + + r, g, b = color[:3] + color_norm = np.array([r, g, b, 255.0], dtype=np.float32) / 255.0 + + if len(path) == 0: + return image + + num_ghosts = 3 + num_points = len(path) + + all_offsets = [get_quantum_offsets(num_points, spread) for _ in range(num_ghosts)] + + # Main path + main_region = utils.points_within_radius(path, radius, border=(height, width)) + if len(main_region) > 0: + ys, xs = main_region[:, 0], main_region[:, 1] + patch = image[ys, xs].astype(np.float32) / 255.0 + alpha = strength + patch[..., :3] = (1 - alpha) * patch[..., :3] + alpha * color_norm[:3] + patch[..., 3] = np.maximum(patch[..., 3], alpha) + image[ys, xs] = (patch * 255).astype(np.uint8) + + # Ghost paths + for ghost_idx in range(num_ghosts): + offsets = all_offsets[ghost_idx] + ghost_path = [] + for pt_idx, pt in enumerate(path): + off_x, off_y = offsets[pt_idx] + ghost_y = int(np.clip(pt[0] + off_y, 0, height - 1)) + ghost_x = int(np.clip(pt[1] + off_x, 0, width - 1)) + ghost_path.append([ghost_y, ghost_x]) + + ghost_path = np.array(ghost_path) + ghost_radius = max(1, int(radius * 0.7)) + ghost_region = utils.points_within_radius(ghost_path, ghost_radius, border=(height, width)) + + if len(ghost_region) > 0: + g_ys, g_xs = ghost_region[:, 0], ghost_region[:, 1] + g_patch = image[g_ys, g_xs].astype(np.float32) / 255.0 + g_alpha = strength * 0.4 + g_patch[..., :3] = (1 - g_alpha) * g_patch[..., :3] + g_alpha * color_norm[:3] + g_patch[..., 3] = np.maximum(g_patch[..., 3], g_alpha) + image[g_ys, g_xs] = (g_patch * 255).astype(np.uint8) + + return image diff --git a/effect/superposition/superposition_requirements.json b/effect/superposition/superposition_requirements.json new file mode 100644 index 0000000..aeb08b9 --- /dev/null +++ b/effect/superposition/superposition_requirements.json @@ -0,0 +1,44 @@ +{ + "name": "Superposition", + "id": "superposition", + "author": "Antigravity", + "version": "1.0.0", + "long_description": "The Superposition brush visualizes the quantum mechanical principle of superposition, where a particle can exist in multiple states simultaneously until observed. When you draw a stroke, the brush uses a quantum circuit with Hadamard gates to generate multiple simultaneous brush traces, spread out probabilistically. The stroke is split into 'ghost' states, representing the quantum probability distribution.", + "description": "Visualizes quantum superposition by scattering the brush path into multiple simultaneous probabilistic ghost paths.\n\nParameters:\n \ud83d\udd39 Radius \u2192 sets the size of each ghost stroke\n \ud83d\udd39 Strength \u2192 controls the opacity of the ghost strokes\n \ud83d\udd39 Spread \u2192 defines how far apart the superposition states are distributed\n \ud83d\udd39 Color \u2192 sets the base color of the brush", + "dependencies": { + "numpy": ">=1.20.0", + "qiskit": ">=1.0.0", + "qiskit_aer": ">=0.17.0" + }, + "user_input": { + "Radius": { + "type": "int", + "min": 1, + "max": 50, + "default": 10 + }, + "Strength": { + "type": "float", + "min": 0.1, + "max": 1.0, + "default": 0.5 + }, + "Spread": { + "type": "int", + "min": 1, + "max": 100, + "default": 30 + }, + "Color": { + "type": "color", + "default": "#00FFFF" + } + }, + "stroke_input": { + "image_rgba": "array", + "path": "array" + }, + "flags": { + "smooth_path": true + } +} From 9c8075e9c6fb349ca4bbbb14eccc7c611787e1de Mon Sep 17 00:00:00 2001 From: axif Date: Sat, 21 Feb 2026 03:40:45 +0600 Subject: [PATCH 2/2] chore: Remove unused `os` import from superposition effect. --- effect/superposition/superposition.py | 1 - 1 file changed, 1 deletion(-) diff --git a/effect/superposition/superposition.py b/effect/superposition/superposition.py index 183a318..8befaee 100644 --- a/effect/superposition/superposition.py +++ b/effect/superposition/superposition.py @@ -2,7 +2,6 @@ from qiskit import QuantumCircuit, transpile from qiskit_aer import AerSimulator import importlib.util -import os spec = importlib.util.spec_from_file_location("utils", "effect/utils.py") utils = importlib.util.module_from_spec(spec)