A macOS image transformation pipeline builder powered by ActionUI and Pillow.
Load an image, stack transformation plugins in any order, tweak parameters in real time, and preview the result. When satisfied, export the pipeline as a standalone Python script.
- macOS 14.6+ (arm64 or x86_64)
- Python 3.8+ with development headers (
python3-configmust work) - Xcode with command-line tools
- Pillow —
pip3 install Pillow - actionui — the ActionUI Python module. It programmatically creates, configures, and launches a native macOS app with SwiftUI views defined via JSON.
pip3 install PillowThe actionui module is not published as a pip package. You need to build it from the ActionUI sources.
Clone or download the ActionUI repository, then run the build script:
cd ActionUI/ActionUIPython
./build_and_install.shThis builds the required static frameworks as Release universal (arm64 + x86_64) via xcodebuild and installs the actionui Python module with pip.
See ActionUIPython/BUILD_GUIDE.md for manual build steps and details.
python3 PillowUI.pyThe pipeline is an ordered list of steps. Each step references a plugin module with its own parameter values. The full pipeline executes sequentially on the source image — each step's output feeds the next step's input.
Export generates a standalone .py script that reproduces the current pipeline using only Pillow — no PillowUI or ActionUI dependency required. The script includes all transformation steps with their parameter values baked in, plus output format and quality settings.
Import lets you load a previously exported .py pipeline back into PillowUI. All steps, parameter values, output format, and quality settings are restored, so you can review, tweak, and re-export the pipeline.
- Auto Contrast — Normalize image contrast automatically
- Border — Add a solid-color border around the image
- Box Blur — Apply box blur (average of neighboring pixels)
- Brightness — Adjust image brightness
- Color Saturation — Adjust image color saturation
- Color Temperature — Shift color temperature warm or cool via per-channel LUTs
- Colorize — Map grayscale values to a two-color gradient (tinting effect)
- Contour — Trace contours to create a line-drawing effect
- Contrast — Adjust image contrast
- Crop — Crop image by percentage from each edge
- Detail — Enhance fine detail in the image
- Edge Enhance — Subtly enhance edges while preserving the image
- Emboss — Apply 3D embossed effect
- Equalize — Histogram equalization for uniform tonal distribution
- Find Edges — Detect and highlight edges in the image
- Flip — Mirror image horizontally or vertically
- Gaussian Blur — Apply Gaussian blur
- Grayscale — Convert image to grayscale
- Invert — Negate all pixel values
- Max Filter — Pick the brightest pixel in each neighborhood (dilate bright features)
- Median Filter — Reduce noise by replacing each pixel with the median of its neighbors
- Min Filter — Pick the darkest pixel in each neighborhood (erode bright features)
- Mode Filter — Replace each pixel with the most frequent value in its neighborhood
- Pad — Resize image to fit within a target size, padding the remainder
- Posterize — Reduce color depth by limiting bits per channel
- Quantize — Reduce to a limited number of colors (palette effect)
- Resize — Resize image by percentage
- Rotate — Rotate image by angle
- Self Blend — Blend the image with itself using Multiply or Screen
- Sharpen (Filter) — Convolution-based sharpening (different from enhancement sharpness)
- Sharpness — Adjust image sharpness
- Smooth — Apply light smoothing to reduce noise
- Solarize — Invert pixels above a brightness threshold
- Spread — Randomly displace pixels for a frosted-glass effect
- Text Watermark — Overlay text on the image
- Threshold — Convert to binary black & white using a brightness cutoff
- Unsharp Mask — Professional sharpening with radius, strength, and threshold
Each plugin can be added to the pipeline multiple times with different settings (e.g., two watermarks at different positions).
Plugins live in the plugins/ directory. Each plugin is a Python module exporting:
PLUGIN_NAME— display namePLUGIN_PARAMS— list of parameter definitions (type, default, min/max, label)transform(image, params)— receives a PIL Image and returns the transformed Imageexport_code(params)/export_imports()— for standalone script generation
Plugins are discovered at launch by scanning plugins/ for .py files (excluding _-prefixed). The app loads them via importlib and registers any module that has both PLUGIN_NAME and transform.
Plugin parameter UIs are generated at runtime from PLUGIN_PARAMS definitions:
-
ActionUI JSON generation — At launch,
generate_plugin_params_json()converts each plugin'sPLUGIN_PARAMSinto an ActionUI JSON layout file ({plugin}_params.json). Float params become[Label | Slider | TextField]rows; bool params become Toggles. These files are written toplugins/and cleaned up on exit. -
ActionUI LoadableView — The main window JSON contains a
LoadableView(a container that loads its content from a JSON file at runtime). When the user selects a pipeline step, the app points the LoadableView at that plugin's generated JSON file viaset_string(). ActionUI parses the JSON and renders native SwiftUI controls. -
Bidirectional sync — Sliders and TextFields are wired with
valueChangeActionIDactions. Moving a slider updates the adjacent TextField; editing the TextField updates the slider position (with min/max clamping). Both paths update the pipeline step's parameter value and re-execute the pipeline. -
ID scheme — Slider IDs start at 1001 and increment per parameter. Corresponding TextField IDs are offset by +1000 (2001, 2002, ...). This allows the action handler to locate both controls for any parameter without collision.

