luster-ko is a simple graphics editing program.
Install luster-ko using the commands, if you use CMake:
cd ./luster-ko
cmake -DCMAKE_INSTALL_PREFIX=/usr
make
make install
luster-ko is distributed under the MIT license.
Users can write Python functions that are automatically exposed in the app’s menus once loaded.
Windows: Unzip the prebuilt package.
Linux/macOS: Build the application from source.
Ensure the corresponding version of Python is installed.
(Optional but recommended) Create a virtual environment for dependencies:
python -m venv venv
source venv/bin/activate # Linux/macOS
venv\Scripts\activate # Windows
Use the provided requirements.txt to install extra libraries (e.g., Hugging Face, OpenCV):
pip install torch==2.7.0+cu126 torchvision==0.22.0 torchaudio==2.7.0 --index-url https://download.pytorch.org/whl/cu126
pip install -r requirements.txt
Place your Python scripts in the designated directory.
Functions without a leading underscore (e.g., def my_tool():) will be exposed in the application’s menus when the script loads successfully.
Scripts can leverage all installed Python libraries.
- ✅ Cross-platform (Windows, Linux, macOS)
- ✅ Extensible via Python
- ✅ Supports external libs (Hugging Face, OpenCV, etc.)
Luster-ko provides a Python scripting subsystem that allows users to extend the application with custom image-processing functions.
Python functions are:
- Loaded dynamically from script files.
- Inspected automatically.
- Exposed as menu actions.
- Executed asynchronously.
- Connected to the editor's image and markup layers.
The system supports two operating modes:
- Image-only mode
- Image + Markup mode
Markup mode allows Python scripts to receive and process a user-created mask/selection layer in addition to the main image.
Python Script
│
▼
ScriptModel
│
▼
FunctionInfo
│
▼
ScriptEffect
│
▼
ImageArea
├─ Image
└─ Markup
The central classes are:
| Class | Responsibility |
|---|---|
ScriptModel |
Python interpreter management and function execution |
FunctionInfo |
Metadata extracted from Python functions |
ScriptEffect |
Connects menu actions with Python execution |
ScriptEffectSettings |
Generates parameter UI automatically |
ImageArea |
Holds image and markup layers |
When a script is loaded, ScriptModel:
- Starts an embedded Python interpreter (
pybind11). - Executes the script.
- Inspects all public functions.
- Extracts signatures, annotations, defaults, and docstrings.
- Builds a list of
FunctionInfoobjects.
Only public functions are exposed.
Example:
def blur_image(
image: numpy.ndarray,
radius: float = 5.0
):
...becomes a menu item automatically.
The scripting system determines function behavior entirely from parameter types.
def blur_image(
image: numpy.ndarray,
radius: float = 5.0
):
...Detected as:
parameters[0].annotation == "<class 'numpy.ndarray'>"Result:
- Receives current image
- Appears under image effects
- Operates on existing content
def generate_mandelbrot(
width: int,
height: int
):
...Detected as:
parameters.empty() ||
parameters[0].annotation != "<class 'numpy.ndarray'>"via:
bool isCreatingFunction() const
{
return parameters.empty()
|| parameters[0].annotation != "<class 'numpy.ndarray'>";
}Result:
- Creates a new image
- Does not require an existing image
- Opens result in a new tab
Markup is a separate grayscale layer associated with an image.
Users can draw on this layer using markup tools.
Typical uses:
- Selection masks
- Inpainting masks
- Protected regions
- Processing constraints
- Region-of-interest editing
Conceptually:
ImageArea
├── RGB Image
└── Grayscale Markup
Markup support is determined automatically from the function signature.
Implementation:
bool usesMarkup() const
{
return parameters.size() > 1 &&
parameters[1].annotation ==
"<class 'numpy.ndarray'>";
}A function uses markup when:
- Parameter #1 is the image
- Parameter #2 is another NumPy array
Example:
def inpaint(
image: numpy.ndarray,
markup: numpy.ndarray
):
...The second image parameter is interpreted as the markup layer.
When an effect executes:
QVariantList args;
args << image;
if (mFunctionInfo.usesMarkup())
args << markup;Therefore:
def blur_image(image):
...receives:
imagedef inpaint(image, markup):
...receives:
image
markupThe markup is supplied automatically.
No manual retrieval is necessary.
The bridge converts Qt images to NumPy arrays.
QImage
↓
numpy.ndarray
Supported formats:
- RGB images
- Grayscale images
- Float images
The Python side always sees NumPy arrays.
Example:
def process(image):
print(image.shape)Returned NumPy arrays are converted back to:
numpy.ndarray
↓
QImage
and inserted into the editor.
A Python function returns a NumPy array.
Example:
def invert(image):
return 255 - imageThe returned array becomes the new image.
The application determines destination by image format.
When a grayscale image is returned:
if (img.format() == QImage::Format_Grayscale8)
imageArea->setMarkup(img);
else
imageArea->setImage(img);Therefore a script can generate:
- a new image
- a new markup layer
depending on the returned data.
Function parameters automatically generate controls.
Example:
def blur_image(
image,
radius: float = 5.0,
iterations: int = 3,
enabled: bool = True
):
...Produces:
| Python Type | UI Control |
|---|---|
int |
Spin box |
float |
Numeric text box |
double |
Numeric text box |
bool |
Check box |
str |
Text box |
tuple |
Text box |
complex |
Complex editor |
No UI code is required.
Docstrings are parsed automatically.
Example:
def blur_image(
image,
radius: float = 5.0
):
"""
Blur image.
Args:
radius (float):
Blur radius.
"""The parser extracts:
- Function description
- Parameter descriptions
- Parameter types
These values populate the UI and tooltips.
Scripts execute in a worker thread.
QtConcurrent::run(...)Execution flow:
User Action
│
▼
Worker Thread
│
▼
Python Function
│
▼
Result Image
During execution:
- Main window is disabled
- Spinner overlay is shown
- UI remains responsive
The Python runtime exposes:
_send_image(...)A script can send intermediate images:
_send_image(preview)This enables:
- Live previews
- Progress visualization
- Iterative algorithms
Python receives:
_check_interrupt()Example:
for i in range(10000):
if _check_interrupt():
break
...This allows long-running scripts to stop safely.
stdout and stderr are redirected to the application.
Example:
print("Loading model...")appears inside the built-in Python console.
Supported output:
print()- Exceptions
- Warnings
- tqdm progress bars
- Enable Markup Mode
- Draw mask
- Run Python effect
def inpaint(
image,
markup
):
result = model.inpaint(
image,
mask=markup
)
return resultUser draws markup
│
▼
ImageArea
├─ Image
└─ Markup
│
▼
ScriptEffect
│
▼
ScriptModel
│
▼
Python Function
│
▼
NumPy Result
│
▼
QImage
│
▼
Editor Update
The scripting system is built around automatic discovery of Python functions and automatic conversion between Qt images and NumPy arrays.
Key features include:
- Dynamic Python function discovery
- Embedded Python interpreter
- Automatic parameter UI generation
- NumPy image exchange
- Asynchronous execution
- Console output redirection
- Progress image support
- Cancellation support
- Optional markup-mask processing
Markup mode integrates seamlessly with scripting: if a Python function declares a second numpy.ndarray parameter, the editor automatically passes the current markup layer, enabling mask-aware image processing, inpainting, selection-based effects, and region-specific operations.
These Python scripts extend the paint application with image generation, transformation, and enhancement features.
See scripts folder for examples.
This script focuses on depth-guided image transformation. It transforms images using depth maps to maintain scene geometry.
- Functions
generate_depth_image— Transforms an image based on depth information, preserving scene structure.
This script supports image-to-image translation, transforming an existing picture while keeping its overall structure.
- Functions
generate_img2img— Produces a modified version of an input image while maintaining its overall structure.
Uses SD 1.5 img2img, which:
-
Adds noise according to strength
-
Re-generates the image guided by the prompt➡️ No understanding of scene depth.➡️ Geometry/sizes may drift or distort.
Uses SD2 depth-guided diffusion, where the model gets:
-
The RGB image
-
A predicted depth map
-
The prompt
➡️ Much stronger preservation of layout and shapes➡️ Camera angle, perspective, object proportions remain stable
✔ Style transfer
✔ Recoloring, mood changes
✔ Artistic transformations
✔ Turning sketches into paintings
✔ Significant prompt-driven changes (faces, objects, lighting)
✖ Geometry preservation is weak
✖ Objects may shift or deform
✖ Hard for realism with strict constraints
✔ Structure-preserving realism
✔ Maintaining perspective, edges, contours
✔ Photo → enhanced photo
✔ Background replacement with stable foreground
✔ Consistent character/object shape
✔ Keeping hands, body positions, architecture stable
✖ Less flexible for wild artistic transformations
✖ More literal to original image
-
Drift away from the original image at higher strengths
-
Change shapes, edges, even composition
-
Be more creative (good or bad)
-
Stay loyal to the original layout
-
Preserve contours, buildings, bodies
-
Produce realism with stable object boundaries
-
Allow big semantic changes while preserving geometry
strength = how much noise is added
-
0.1 → slight stylization
-
0.5 → strong change
-
0.9 → almost full re-generation
Depth condition provides stability even with high strength
-
0.1 → mild color/stylistic tweaks
-
0.5 → significant transformation but geometry kept
-
0.9 → still retains shapes better than img2img
-
Better for stylization
-
Less realistic
-
More artifacts
-
Weaker at human anatomy
-
Better depth understanding
-
Realistic rendering
-
Much more stable human shapes
-
Preserves backgrounds and structure
-
You want creative variation
-
You want to heavily stylize or reimagine
-
You want surreal / artistic transformations
-
You don't need strict preservation of shapes
-
You want to keep the geometry
-
You want realistic or photographic edits
-
You want to keep perspective and composition stable
-
You want to change style but preserve structure
-
You are editing photos or production artwork
Input: photo of a building
img2img → might reshape windows, add floors, distort lines
depth2img → keeps building straight, only changes textures/colors Input: portrait
img2img → risks face changes
depth2img → keeps same face geometry, pose, lighting
This script provides a basic image generation entry point for creating images from scratch.
- Functions
generate_image— Creates an image from scratch, typically using a generative model (e.g., diffusion).
This script specializes in inpainting, i.e., filling in missing or masked areas with contextually appropriate content.
- Functions
predict— Performs inpainting by filling missing or masked image areas with context-aware content.
This script provides general-purpose image processing and procedural generation tools.
It includes filtering, corrections, resizing, and fractal/texture generators.
- Functions
denoise_image— Reduces unwanted noise or grain in an image.blur_image— Applies a blur filter.deblur_image— Restores sharpness to a blurred image.gamma_correct— Adjusts brightness and contrast through gamma correction.enhance_contrast— Improves visibility of details by adjusting contrast.resize_image— Rescales an image to new dimensions.generate_mandelbrot— Produces a Mandelbrot fractal image.generate_julia— Produces a Julia set fractal image.generate_plasma— Creates a plasma-like procedural texture.
This script implements style transfer, allowing users to apply the artistic style of one image to another.
- Functions
set_as_style— Selects an image as a reference style.transfer_style— Transfers the reference style onto another image.
Enable Instruments -> Markup mode to use markup:

