Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions effect/superposition/README.md
Original file line number Diff line number Diff line change
@@ -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.
Empty file.
91 changes: 91 additions & 0 deletions effect/superposition/superposition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
import importlib.util

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
44 changes: 44 additions & 0 deletions effect/superposition/superposition_requirements.json
Original file line number Diff line number Diff line change
@@ -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
}
}