zignal
zero-dependency image processing library.
Solve the assignment problem using the Hungarian algorithm.
Finds the optimal one-to-one assignment that minimizes or maximizes the total cost in O(n³) time. Handles both square and rectangular matrices.
Parameters
cost_matrix
(Matrix
): Cost matrix where element (i,j) is the cost of assigning row i to column jpolicy
(OptimizationPolicy
): Whether to minimize or maximize total cost (default: MIN)
Returns
Assignment
: Object containing the optimal assignments and total cost
Examples
from zignal import Matrix, OptimizationPolicy, solve_assignment_problem
matrix = Matrix([[1, 2, 6], [5, 3, 6], [4, 5, 0]])
for p in [OptimizationPolicy.MIN, OptimizationPolicy.MAX]:
result = solve_assignment_problem(matrix, p)
print("minimum cost") if p == OptimizationPolicy.MIN else print("maximum profit")
print(f" - Total cost: {result.total_cost}")
print(f" - Assignments: {result.assignments}")
Image for processing and manipulation.
Pixel access via indexing returns a proxy object that allows in-place
modification. Use .item()
on the proxy to extract the color value:
pixel = img[row, col] # Returns pixel proxy
color = pixel.item() # Extracts color object (Rgb/Rgba/int)
This object is iterable: iterating yields (row, col, pixel) in native
dtype in row-major order. For bulk numeric work, prefer to_numpy()
.
Create a new Image with the specified dimensions and optional fill color.
Parameters
rows
(int): Number of rows (height) of the imagecols
(int): Number of columns (width) of the imagecolor
(optional): Fill color. Can be:- Integer (0-255) for grayscale
- RGB tuple (r, g, b) with values 0-255
- RGBA tuple (r, g, b, a) with values 0-255
- Any color object (Rgb, Hsl, Hsv, etc.)
- Defaults to transparent (0, 0, 0, 0)
dtype
(type, optional): Pixel data type specifying storage type.zignal.Grayscale
→ single-channel u8 (NumPy shape (H, W, 1))zignal.Rgb
(default) → 3-channel RGB (NumPy shape (H, W, 3))zignal.Rgba
→ 4-channel RGBA (NumPy shape (H, W, 4))
Examples
# Create a 100x200 black image (default RGB)
img = Image(100, 200)
# Create a 100x200 red image (RGBA)
img = Image(100, 200, (255, 0, 0, 255))
# Create a 100x200 grayscale image with mid-gray fill
img = Image(100, 200, 128, dtype=zignal.Grayscale)
# Create a 100x200 RGB image (dtype overrides the color value)
img = Image(100, 200, (0, 255, 0, 255), dtype=zignal.Rgb)
# Create an image from numpy array dimensions
img = Image(*arr.shape[:2])
# Create with semi-transparent blue (requires RGBA)
img = Image(100, 100, (0, 0, 255, 128), dtype=zignal.Rgba)
Load an image from file (PNG or JPEG).
The pixel format (Grayscale, Rgb, or Rgba) is automatically determined from the file metadata. For PNGs, the format matches the file's color type. For JPEGs, grayscale images load as Grayscale, color images as Rgb.
Parameters
path
(str): Path to the PNG or JPEG file to load
Returns
Image: A new Image object with pixels in the format matching the file
Raises
FileNotFoundError
: If the file does not existValueError
: If the file format is unsupportedMemoryError
: If allocation fails during loadingPermissionError
: If read permission is denied
Examples
# Load images with automatic format detection
img = Image.load("photo.png") # May be Rgba
img2 = Image.load("grayscale.jpg") # Will be Grayscale
img3 = Image.load("rgb.png") # Will be Rgb
# Check format after loading
print(img.dtype) # e.g., Rgba, Rgb, or Grayscale
Save the image to a file (PNG or JPEG format).
The format is determined by the file extension (.png, .jpg, or .jpeg).
Parameters
path
(str): Path where the image file will be saved. Must have .png, .jpg, or .jpeg extension.
Raises
ValueError
: If the file has an unsupported extensionMemoryError
: If allocation fails during savePermissionError
: If write permission is deniedFileNotFoundError
: If the directory does not exist
Examples
img = Image.load("input.png")
img.save("output.png") # Save as PNG
img.save("output.jpg") # Save as JPEG
Create a deep copy of the image.
Returns a new Image with the same dimensions and pixel data, but with its own allocated memory.
Examples
img = Image.load("photo.png")
copy = img.copy()
# Modifying copy doesn't affect original
copy[0, 0] = (255, 0, 0)
Fill the entire image with a solid color.
Parameters
color
: Fill color. Can be:- Integer (0-255) for grayscale images
- RGB tuple (r, g, b) with values 0-255
- RGBA tuple (r, g, b, a) with values 0-255
- Any color object (Rgb, Hsl, Hsv, etc.)
Examples
img = Image(100, 100)
img.fill((255, 0, 0)) # Fill with red
Create a view of the image or a sub-region (zero-copy).
Creates a new Image that shares the same underlying pixel data. Changes to the view affect the original image and vice versa.
Parameters
rect
(Rectangle | tuple[float, float, float, float] | None): Optional rectangle defining the sub-region to view. If None, creates a view of the entire image. When providing a tuple, it should be (left, top, right, bottom).
Returns
Image: A view of the image that shares the same pixel data
Examples
img = Image.load("photo.png")
# View entire image
view = img.view()
# View sub-region
rect = Rectangle(10, 10, 100, 100)
sub = img.view(rect)
# Modifications to view affect original
sub.fill((255, 0, 0)) # Fills region in original image
Set the image border outside a rectangle to a value.
Sets pixels outside the given rectangle to the provided color/value, leaving the interior untouched. The rectangle may be provided as a Rectangle or a tuple (left, top, right, bottom). It is clipped to the image bounds.
Parameters
rect
(Rectangle | tuple[float, float, float, float]): Inner rectangle to preserve.color
(optional): Fill value for border. Accepts the same types asfill
. If omitted, uses zeros for the current dtype (0, Rgb(0,0,0), or Rgba(0,0,0,0)).
Examples
img = Image(100, 100)
rect = Rectangle(10, 10, 90, 90)
img.set_border(rect) # zero border
img.set_border(rect, (255, 0, 0)) # red border
# Common pattern: set a uniform 16px border using shrink()
img.set_border(img.get_rectangle().shrink(16))
Check if the image data is stored contiguously in memory.
Returns True if pixels are stored without gaps (stride == cols), False for views or images with custom strides.
Examples
img = Image(100, 100)
print(img.is_contiguous()) # True
view = img.view(Rectangle(10, 10, 50, 50))
print(view.is_contiguous()) # False
Convert the image to a different pixel data type.
Supported targets: Grayscale, Rgb, Rgba.
Returns a new Image with the requested format.
Get a Canvas object for drawing on this image.
Returns a Canvas that can be used to draw shapes, lines, and text directly onto the image pixels.
Examples
img = Image(200, 200)
cv = img.canvas()
cv.draw_circle(100, 100, 50, (255, 0, 0))
cv.fill_rect(10, 10, 50, 50, (0, 255, 0))
Calculate Peak Signal-to-Noise Ratio between two images.
PSNR is a quality metric where higher values indicate greater similarity. Typical values: 30-50 dB (higher is better). Returns infinity for identical images.
Parameters
other
(Image): The image to compare against. Must have same dimensions and dtype.
Returns
float: PSNR value in decibels (dB), or inf for identical images
Raises
ValueError
: If images have different dimensions or dtypes
Examples
original = Image.load("original.png")
compressed = Image.load("compressed.png")
quality = original.psnr(compressed)
print(f"PSNR: {quality:.2f} dB")
Create Image from a NumPy array with dtype uint8.
Zero-copy is used for arrays with these shapes:
- Grayscale: (rows, cols, 1) → Image(Grayscale)
- RGB: (rows, cols, 3) → Image(Rgb)
- RGBA: (rows, cols, 4) → Image(Rgba)
The array can have row strides (e.g., from views or slicing) as long as pixels
within each row are contiguous. For arrays with incompatible strides (e.g., transposed),
use numpy.ascontiguousarray()
first.
Parameters
array
(NDArray[np.uint8]): NumPy array with shape (rows, cols, 1), (rows, cols, 3) or (rows, cols, 4) and dtype uint8. Pixels within rows must be contiguous.
Raises
TypeError
: If array is None or has wrong dtypeValueError
: If array has wrong shape or incompatible strides
Notes
The array can have row strides (padding between rows) but pixels within each row must be contiguous. For incompatible layouts (e.g., transposed arrays), use np.ascontiguousarray() first:
arr = np.ascontiguousarray(arr)
img = Image.from_numpy(arr)
Examples
arr = np.zeros((100, 200, 3), dtype=np.uint8)
img = Image.from_numpy(arr)
print(img.rows, img.cols)
# Output: 100 200
Convert the image to a NumPy array (zero-copy when possible).
Returns an array in the image's native dtype:
Grayscale → shape (rows, cols, 1)
Rgb → shape (rows, cols, 3)
Rgba → shape (rows, cols, 4)
Examples
img = Image.load("photo.png")
arr = img.to_numpy()
print(arr.shape, arr.dtype)
# Example: (H, W, C) uint8 where C is 1, 3, or 4
Resize the image to the specified size.
Parameters
size
(float or tuple[int, int]):- If float: scale factor (e.g., 0.5 for half size, 2.0 for double size)
- If tuple: target dimensions as (rows, cols)
method
(Interpolation
, optional): Interpolation method to use. Default isInterpolation.BILINEAR
.
Resize image to fit within the specified size while preserving aspect ratio.
The image is scaled to fit within the target dimensions and centered with black borders (letterboxing) to maintain the original aspect ratio.
Parameters
size
(int or tuple[int, int]):- If int: creates a square output of size x size
- If tuple: target dimensions as (rows, cols)
method
(Interpolation
, optional): Interpolation method to use. Default isInterpolation.BILINEAR
.
Rotate the image by the specified angle around its center.
The output image is automatically sized to fit the entire rotated image without clipping.
Parameters
angle
(float): Rotation angle in radians counter-clockwise.method
(Interpolation
, optional): Interpolation method to use. Default isInterpolation.BILINEAR
.
Examples
import math
img = Image.load("photo.png")
# Rotate 45 degrees with default bilinear interpolation
rotated = img.rotate(math.radians(45))
# Rotate 90 degrees with nearest neighbor (faster, lower quality)
rotated = img.rotate(math.radians(90), Interpolation.NEAREST_NEIGHBOR)
# Rotate -30 degrees with Lanczos (slower, higher quality)
rotated = img.rotate(math.radians(-30), Interpolation.LANCZOS)
Apply a geometric transform to the image.
This method warps an image using a geometric transform (Similarity, Affine, or Projective). For each pixel in the output image, it applies the transform to find the corresponding location in the source image and samples using the specified interpolation method.
Parameters
transform
: A geometric transform object (SimilarityTransform, AffineTransform, or ProjectiveTransform)shape
(optional): Output image shape as (rows, cols) tuple. Defaults to input image shape.method
(optional): Interpolation method. Defaults to Interpolation.BILINEAR.
Examples
# Apply similarity transform
from_points = [(0, 0), (100, 0), (100, 100)]
to_points = [(10, 10), (110, 20), (105, 115)]
transform = SimilarityTransform(from_points, to_points)
warped = img.warp(transform)
# Apply with custom output size and interpolation
warped = img.warp(transform, shape=(512, 512), method=Interpolation.BICUBIC)
Flip image left-to-right (horizontal mirror).
Returns a new image that is a horizontal mirror of the original.
flipped = img.flip_left_right()
Flip image top-to-bottom (vertical mirror).
Returns a new image that is a vertical mirror of the original.
flipped = img.flip_top_bottom()
Extract a rectangular region from the image.
Returns a new Image containing the cropped region. Pixels outside the original image bounds are filled with transparent black (0, 0, 0, 0).
Parameters
rect
(Rectangle): The rectangular region to extract
Examples
img = Image.load("photo.png")
rect = Rectangle(10, 10, 110, 110) # 100x100 region starting at (10, 10)
cropped = img.crop(rect)
print(cropped.rows, cropped.cols) # 100 100
Extract a rotated rectangular region from the image and resample it.
Returns a new Image containing the extracted and resampled region.
Parameters
rect
(Rectangle): The rectangular region to extract (before rotation)angle
(float, optional): Rotation angle in radians (counter-clockwise). Default: 0.0size
(int or tuple[int, int], optional). If not specified, uses the rectangle's dimensions.- If int: output is a square of side
size
- If tuple: output size as (rows, cols)
- If int: output is a square of side
method
(Interpolation, optional): Interpolation method. Default: BILINEAR
Examples
import math
img = Image.load("photo.png")
rect = Rectangle(10, 10, 110, 110)
# Extract without rotation
extracted = img.extract(rect)
# Extract with 45-degree rotation
rotated = img.extract(rect, angle=math.radians(45))
# Extract and resize to specific dimensions
resized = img.extract(rect, size=(50, 75))
# Extract to a 64x64 square
square = img.extract(rect, size=64)
Insert a source image into this image at a specified rectangle with optional rotation.
This method modifies the image in-place.
Parameters
source
(Image): The image to insertrect
(Rectangle): Destination rectangle where the source will be placedangle
(float, optional): Rotation angle in radians (counter-clockwise). Default: 0.0method
(Interpolation, optional): Interpolation method. Default: BILINEAR
Examples
import math
canvas = Image(500, 500)
logo = Image.load("logo.png")
# Insert at top-left
rect = Rectangle(10, 10, 110, 110)
canvas.insert(logo, rect)
# Insert with rotation
rect2 = Rectangle(200, 200, 300, 300)
canvas.insert(logo, rect2, angle=math.radians(45))
Apply a box blur to the image.
Parameters
radius
(int): Non-negative blur radius in pixels.0
returns an unmodified copy.
Examples
img = Image.load("photo.png")
soft = img.box_blur(2)
identity = img.box_blur(0) # no-op copy
Apply Gaussian blur to the image.
Parameters
sigma
(float): Standard deviation of the Gaussian kernel. Must be > 0.
Examples
img = Image.load("photo.png")
blurred = img.gaussian_blur(2.0)
blurred_soft = img.gaussian_blur(5.0) # More blur
Apply Difference of Gaussians (DoG) filter to the image.
Computes the difference between two Gaussian blurs with different sigmas. This is commonly used for edge detection and as an approximation of the Laplacian of Gaussian. The result highlights regions of rapid intensity change and is useful for feature enhancement and blob detection.
Parameters
sigma1
(float): Standard deviation of the first (typically smaller) Gaussian kernel. Must be > 0.sigma2
(float): Standard deviation of the second (typically larger) Gaussian kernel. Must be > 0 and != sigma1.offset
(int, optional): For u8/RGB/RGBA images, shift the output by a constant to preserve negative values.- Default: 128 for u8-based images, 0 otherwise. Set to 0 to match libraries that clamp to [0,255].
Returns
A new image containing the difference of the two Gaussian-blurred versions.
Notes
- For edge detection, typically sigma2 ≈ 1.6 * sigma1
- Values are clamped to the valid range (0-255)
Examples
img = Image.load("photo.png")
# Standard edge detection ratio
edges = img.difference_of_gaussians(1.0, 1.6)
# Fine detail enhancement
fine_details = img.difference_of_gaussians(0.5, 1.0)
# Coarse feature detection
coarse_features = img.difference_of_gaussians(2.0, 3.2)
Sharpen the image using unsharp masking (2 * self - blur_box).
Parameters
radius
(int): Non-negative blur radius used to compute the unsharp mask.0
returns an unmodified copy.
Examples
img = Image.load("photo.png")
crisp = img.sharpen(2)
identity = img.sharpen(0) # no-op copy
Invert the colors of the image.
Creates a negative/inverted version of the image where:
- Grayscale pixels: 255 - value
- RGB pixels: inverts each channel (255 - r, 255 - g, 255 - b)
- RGBA pixels: inverts RGB channels while preserving alpha
Examples
img = Image.load("photo.png")
inverted = img.invert()
# Works with all image types
gray = Image(100, 100, 128, dtype=zignal.Grayscale)
gray_inv = gray.invert() # pixels become 127
Automatically adjust image contrast by stretching the intensity range.
Analyzes the histogram and remaps pixel values so the darkest pixels become black (0) and brightest become white (255).
Parameters
cutoff
(float, optional): Percentage of pixels to ignore at extremes (0-50). Default: 0. For example, 2.0 ignores the darkest and brightest 2% of pixels, helping to remove outliers.
Returns
A new image with adjusted contrast.
Examples
img = Image.load("photo.png")
# Basic auto-contrast
enhanced = img.autocontrast()
# Ignore 2% outliers on each end
enhanced = img.autocontrast(cutoff=2.0)
Equalize the histogram of the image to improve contrast.
Redistributes pixel intensities to achieve a more uniform histogram, which typically enhances contrast in images with poor contrast or uneven lighting conditions. The technique maps the cumulative distribution function (CDF) of pixel values to create a more even spread of intensities across the full range.
For color images (RGB/RGBA), each channel is equalized independently.
Returns
Image: New image with equalized histogram
Example
import zignal
# Load an image with poor contrast
img = zignal.Image.load("low_contrast.jpg")
# Apply histogram equalization
equalized = img.equalize()
# Save the result
equalized.save("equalized.jpg")
# Compare with autocontrast
auto = img.autocontrast(cutoff=2.0)
Apply motion blur effect to the image.
Motion blur simulates camera or object movement during exposure. Three types of motion blur are supported:
MotionBlur.linear()
- Linear motion blurMotionBlur.radial_zoom()
- Radial zoom blurMotionBlur.radial_spin()
- Radial spin blur
Examples
from zignal import Image, MotionBlur
import math
img = Image.load("photo.png")
# Linear motion blur examples
horizontal_blur = img.motion_blur(MotionBlur.linear(angle=0, distance=30)) # Camera panning
vertical_blur = img.motion_blur(MotionBlur.linear(angle=math.pi/2, distance=20)) # Camera shake
diagonal_blur = img.motion_blur(MotionBlur.linear(angle=math.pi/4, distance=25)) # Diagonal motion
# Radial zoom blur examples
center_zoom = img.motion_blur(MotionBlur.radial_zoom(center=(0.5, 0.5), strength=0.7)) # Center zoom burst
off_center_zoom = img.motion_blur(MotionBlur.radial_zoom(center=(0.33, 0.67), strength=0.5)) # Rule of thirds
subtle_zoom = img.motion_blur(MotionBlur.radial_zoom(strength=0.3)) # Subtle effect with defaults
# Radial spin blur examples
center_spin = img.motion_blur(MotionBlur.radial_spin(center=(0.5, 0.5), strength=0.5)) # Center rotation
swirl_effect = img.motion_blur(MotionBlur.radial_spin(center=(0.3, 0.3), strength=0.6)) # Off-center swirl
strong_spin = img.motion_blur(MotionBlur.radial_spin(strength=0.8)) # Strong spin with defaults
Notes
- Linear blur preserves image dimensions
- Radial effects use bilinear interpolation for smooth results
- Strength values closer to 1.0 produce stronger blur effects
Apply Sobel edge detection and return the gradient magnitude.
The result is a new grayscale image (dtype=zignal.Grayscale
) where
each pixel encodes the edge strength at that location.
Examples
img = Image.load("photo.png")
edges = img.sobel()
Apply Shen-Castan edge detection to the image.
The Shen-Castan algorithm uses ISEF (Infinite Symmetric Exponential Filter) for edge detection with adaptive gradient computation and hysteresis thresholding. Returns a binary edge map where edges are 255 (white) and non-edges are 0 (black).
Parameters
smooth
(float, optional): ISEF smoothing factor (0 < smooth < 1). Higher values preserve more detail. Default: 0.9window_size
(int, optional): Odd window size for local gradient statistics (>= 3). Default: 7high_ratio
(float, optional): Percentile for high threshold selection (0 < high_ratio < 1). Default: 0.99low_rel
(float, optional): Low threshold as fraction of high threshold (0 < low_rel < 1). Default: 0.5hysteresis
(bool, optional): Enable hysteresis edge linking. When True, weak edges connected to strong edges are preserved. Default: Trueuse_nms
(bool, optional): Use non-maximum suppression for single-pixel edges. When True, produces thinner edges. Default: False
Returns
Image
: Binary edge map (Grayscale image with values 0 or 255)
Examples
from zignal import Image
img = Image.load("photo.jpg")
# Use default settings
edges = img.shen_castan()
# Low-noise settings for clean images
clean_edges = img.shen_castan(smooth=0.95, high_ratio=0.98)
# High-noise settings for noisy images
denoised_edges = img.shen_castan(smooth=0.7, window_size=11)
# Single-pixel edges with NMS
thin_edges = img.shen_castan(use_nms=True)
Blend an overlay image onto this image using the specified blend mode.
Modifies this image in-place. Both images must have the same dimensions. The overlay image must have an alpha channel for proper blending.
Parameters
overlay
(Image): Image to blend onto this imagemode
(Blending, optional): Blending mode (default: NORMAL)
Raises
ValueError
: If images have different dimensionsTypeError
: If overlay is not an Image object
Examples
# Basic alpha blending
base = Image(100, 100, (255, 0, 0))
overlay = Image(100, 100, (0, 0, 255, 128)) # Semi-transparent blue
base.blend(overlay) # Default NORMAL mode
# Using different blend modes
base.blend(overlay, zignal.Blending.MULTIPLY)
base.blend(overlay, zignal.Blending.SCREEN)
base.blend(overlay, zignal.Blending.OVERLAY)
Matrix for numerical computations with f64 (float64) values.
This class provides a bridge between zignal's Matrix type and NumPy arrays, with zero-copy operations when possible.
Examples
import zignal
import numpy as np
# Create from list of lists
m = zignal.Matrix([[1, 2, 3], [4, 5, 6]])
# Create with dimensions using full()
m = zignal.Matrix.full(3, 4) # 3x4 matrix of zeros
m = zignal.Matrix.full(3, 4, fill_value=1.0) # filled with 1.0
# From numpy (zero-copy for float64 contiguous arrays)
arr = np.random.randn(10, 5)
m = zignal.Matrix.from_numpy(arr)
# To numpy (zero-copy)
arr = m.to_numpy()
Create a new Matrix from a list of lists.
Parameters
data
(List[List[float]]): List of lists containing matrix data
Examples
# Create from list of lists
m = Matrix([[1, 2, 3], [4, 5, 6]]) # 2x3 matrix
m = Matrix([[1.0, 2.5], [3.7, 4.2]]) # 2x2 matrix
Create a Matrix filled with a specified value.
Parameters
rows
(int): Number of rowscols
(int): Number of columnsfill_value
(float, optional): Value to fill the matrix with (default: 0.0)
Returns
Matrix: A new Matrix of the specified dimensions filled with fill_value
Examples
# Create 3x4 matrix of zeros
m = Matrix.full(3, 4)
# Create 3x4 matrix of ones
m = Matrix.full(3, 4, 1.0)
# Create 5x5 matrix filled with 3.14
m = Matrix.full(5, 5, 3.14)
Create a Matrix from a NumPy array (zero-copy when possible).
The array must be 2D with dtype float64 and be C-contiguous. If the array is not contiguous or not float64, an error is raised.
Parameters
array
(NDArray[np.float64]): A 2D NumPy array with dtype float64
Returns
Matrix: A new Matrix that shares memory with the NumPy array
Examples
import numpy as np
arr = np.random.randn(10, 5) # float64 by default
m = Matrix.from_numpy(arr)
# Modifying arr will modify m and vice versa
Convert the matrix to a NumPy array (zero-copy).
Returns a float64 NumPy array that shares memory with the Matrix. Modifying the array will modify the Matrix.
Returns
NDArray[np.float64]: A 2D NumPy array with shape (rows, cols)
Examples
m = Matrix(3, 4, fill_value=1.0)
arr = m.to_numpy() # shape (3, 4), dtype float64
A rectangle defined by its left, top, right, and bottom coordinates.
Initialize a Rectangle with specified coordinates.
Creates a rectangle from its bounding coordinates. The rectangle is defined by four values: left (x-min), top (y-min), right (x-max), and bottom (y-max). The right and bottom bounds are exclusive.
Parameters
left
(float): Left edge x-coordinate (inclusive)top
(float): Top edge y-coordinate (inclusive)right
(float): Right edge x-coordinate (exclusive)bottom
(float): Bottom edge y-coordinate (exclusive)
Examples
# Create a rectangle from (10, 20) to (110, 70)
rect = Rectangle(10, 20, 110, 70)
print(rect.width) # 100.0 (110 - 10)
print(rect.height) # 50.0 (70 - 20)
print(rect.contains(109.9, 69.9)) # True
print(rect.contains(110, 70)) # False
# Create a square
square = Rectangle(0, 0, 50, 50)
print(square.width) # 50.0
Notes
- The constructor validates that right >= left and bottom >= top
- Use Rectangle.init_center() for center-based construction
- Coordinates follow image convention: origin at top-left, y increases downward
- Right and bottom bounds are exclusive
Create a Rectangle from center coordinates.
Parameters
x
(float): Center x coordinatey
(float): Center y coordinatewidth
(float): Rectangle widthheight
(float): Rectangle height
Examples
# Create a 100x50 rectangle centered at (50, 50)
rect = Rectangle.init_center(50, 50, 100, 50)
# This creates Rectangle(0, 25, 100, 75)
Check if the rectangle is ill-formed (empty).
A rectangle is considered empty if its left >= right or top >= bottom.
Examples
rect1 = Rectangle(0, 0, 100, 100)
print(rect1.is_empty()) # False
rect2 = Rectangle(100, 100, 100, 100)
print(rect2.is_empty()) # True
Calculate the area of the rectangle.
Examples
rect = Rectangle(0, 0, 100, 50)
print(rect.area()) # 5000.0
Check if a point is inside the rectangle.
Uses exclusive bounds for right and bottom edges.
Parameters
x
(float): X coordinate to checky
(float): Y coordinate to check
Examples
rect = Rectangle(0, 0, 100, 100)
print(rect.contains(50, 50)) # True - inside
print(rect.contains(100, 50)) # False - on right edge (exclusive)
print(rect.contains(99.9, 99.9)) # True - just inside
print(rect.contains(150, 50)) # False - outside
Create a new rectangle expanded by the given amount.
Parameters
amount
(float): Amount to expand each border by
Examples
rect = Rectangle(50, 50, 100, 100)
grown = rect.grow(10)
# Creates Rectangle(40, 40, 110, 110)
Create a new rectangle shrunk by the given amount.
Parameters
amount
(float): Amount to shrink each border by
Examples
rect = Rectangle(40, 40, 110, 110)
shrunk = rect.shrink(10)
# Creates Rectangle(50, 50, 100, 100)
Calculate the intersection of this rectangle with another.
Parameters
other
(Rectangle | tuple[float, float, float, float]): The other rectangle to intersect with
Examples
rect1 = Rectangle(0, 0, 100, 100)
rect2 = Rectangle(50, 50, 150, 150)
intersection = rect1.intersect(rect2)
# Returns Rectangle(50, 50, 100, 100)
# Can also use a tuple
intersection = rect1.intersect((50, 50, 150, 150))
# Returns Rectangle(50, 50, 100, 100)
rect3 = Rectangle(200, 200, 250, 250)
result = rect1.intersect(rect3) # Returns None (no overlap)
Calculate the Intersection over Union (IoU) with another rectangle.
Parameters
other
(Rectangle | tuple[float, float, float, float]): The other rectangle to calculate IoU with
Returns
float
: IoU value between 0.0 (no overlap) and 1.0 (identical rectangles)
Examples
rect1 = Rectangle(0, 0, 100, 100)
rect2 = Rectangle(50, 50, 150, 150)
iou = rect1.iou(rect2) # Returns ~0.143
# Can also use a tuple
iou = rect1.iou((0, 0, 100, 100)) # Returns 1.0 (identical)
# Non-overlapping rectangles
rect3 = Rectangle(200, 200, 250, 250)
iou = rect1.iou(rect3) # Returns 0.0
Check if this rectangle overlaps with another based on IoU and coverage thresholds.
Parameters
other
(Rectangle | tuple[float, float, float, float]): The other rectangle to check overlap withiou_thresh
(float, optional): IoU threshold for considering overlap. Default: 0.5coverage_thresh
(float, optional): Coverage threshold for considering overlap. Default: 1.0
Returns
bool
: True if rectangles overlap enough based on the thresholds
Description
Returns True if any of these conditions are met:
- IoU > iou_thresh
- intersection.area / self.area > coverage_thresh
- intersection.area / other.area > coverage_thresh
Examples
rect1 = Rectangle(0, 0, 100, 100)
rect2 = Rectangle(50, 50, 150, 150)
# Default thresholds
overlaps = rect1.overlaps(rect2) # Uses IoU > 0.5
# Custom IoU threshold
overlaps = rect1.overlaps(rect2, iou_thresh=0.1) # True
# Coverage threshold (useful for small rectangle inside large)
small = Rectangle(25, 25, 75, 75)
overlaps = rect1.overlaps(small, coverage_thresh=0.9) # True (small is 100% covered)
# Can use tuple
overlaps = rect1.overlaps((50, 50, 150, 150), iou_thresh=0.1)
Convex hull computation using Graham's scan algorithm.
Initialize a new ConvexHull instance.
Creates a new ConvexHull instance that can compute the convex hull of 2D point sets using Graham's scan algorithm. The algorithm has O(n log n) time complexity where n is the number of input points.
Examples
# Create a ConvexHull instance
hull = ConvexHull()
# Find convex hull of points
points = [(0, 0), (1, 1), (2, 2), (3, 1), (4, 0), (2, 4), (1, 3)]
result = hull.find(points)
# Returns: [(0.0, 0.0), (1.0, 3.0), (2.0, 4.0), (4.0, 0.0)]
Notes
- Returns vertices in clockwise order
- Returns None for degenerate cases (e.g., all points collinear)
- Requires at least 3 points for a valid hull
Find the convex hull of a set of 2D points.
Returns the vertices of the convex hull in clockwise order as a list of (x, y) tuples, or None if the hull is degenerate (e.g., all points are collinear).
Parameters
points
(list[tuple[float, float]]): List of (x, y) coordinate pairs. At least 3 points are required.
Examples
hull = ConvexHull()
points = [(0, 0), (1, 1), (2, 2), (3, 1), (4, 0), (2, 4), (1, 3)]
result = hull.find(points)
# Returns: [(0.0, 0.0), (1.0, 3.0), (2.0, 4.0), (4.0, 0.0)]
Similarity transform (rotation + uniform scale + translation)
Create similarity transform from point correspondences.
Affine transform (general 2D linear transform)
Create affine transform from point correspondences.
Projective transform (homography/perspective transform)
Create projective transform from point correspondences.
Canvas for drawing operations on images.
Create a Canvas for drawing operations on an Image.
A Canvas provides drawing methods to modify the pixels of an Image. The Canvas maintains a reference to the parent Image to prevent it from being garbage collected while drawing operations are in progress.
Parameters
image
(Image): The Image object to draw on. Must be initialized with dimensions.
Examples
# Create an image and get its canvas
img = Image(100, 100, Rgb(255, 255, 255))
canvas = Canvas(img)
# Draw on the canvas
canvas.fill(Rgb(0, 0, 0))
canvas.draw_circle((50, 50), 20, Rgb(255, 0, 0))
Notes
- The Canvas holds a reference to the parent Image
- All drawing operations modify the original Image pixels
- Use Image.canvas() method as a convenient way to create a Canvas
Fill the entire canvas with a color.
Parameters
color
(int, tuple or color object): Color to fill the canvas with. Can be:
Examples
img = Image.load("photo.png")
canvas = img.canvas()
canvas.fill(128) # Fill with gray
canvas.fill((255, 0, 0)) # Fill with red
canvas.fill(Rgb(0, 255, 0)) # Fill with green using Rgb object
Draw a line between two points.
Parameters
p1
(tuple[float, float]): Starting point coordinates (x, y)p2
(tuple[float, float]): Ending point coordinates (x, y)color
(int, tuple or color object): Color of the line.width
(int, optional): Line width in pixels (default: 1)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Draw a rectangle outline.
Parameters
rect
(Rectangle): Rectangle object defining the boundscolor
(int, tuple or color object): Color of the rectangle.width
(int, optional): Line width in pixels (default: 1)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Fill a rectangle area.
Parameters
rect
(Rectangle): Rectangle object defining the boundscolor
(int, tuple or color object): Fill color.mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Draw a polygon outline.
Parameters
points
(list[tuple[float, float]]): List of (x, y) coordinates forming the polygoncolor
(int, tuple or color object): Color of the polygon.width
(int, optional): Line width in pixels (default: 1)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Fill a polygon area.
Parameters
points
(list[tuple[float, float]]): List of (x, y) coordinates forming the polygoncolor
(int, tuple or color object): Fill color.mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Draw a circle outline.
Parameters
center
(tuple[float, float]): Center coordinates (x, y)radius
(float): Circle radiuscolor
(int, tuple or color object): Color of the circle.width
(int, optional): Line width in pixels (default: 1)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Fill a circle area.
Parameters
center
(tuple[float, float]): Center coordinates (x, y)radius
(float): Circle radiuscolor
(int, tuple or color object): Fill color.mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Draw an arc outline.
Parameters
center
(tuple[float, float]): Center coordinates (x, y)radius
(float): Arc radius in pixelsstart_angle
(float): Starting angle in radians (0 = right, π/2 = down, π = left, 3π/2 = up)end_angle
(float): Ending angle in radianscolor
(int, tuple or color object): Color of the arc.width
(int, optional): Line width in pixels (default: 1)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Notes
- Angles are measured in radians, with 0 pointing right and increasing clockwise
- For a full circle, use start_angle=0 and end_angle=2π
- The arc is drawn from start_angle to end_angle in the positive angular direction
Fill an arc (pie slice) area.
Parameters
center
(tuple[float, float]): Center coordinates (x, y)radius
(float): Arc radius in pixelsstart_angle
(float): Starting angle in radians (0 = right, π/2 = down, π = left, 3π/2 = up)end_angle
(float): Ending angle in radianscolor
(int, tuple or color object): Fill color.mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Notes
- Creates a filled pie slice from the center to the arc edge
- Angles are measured in radians, with 0 pointing right and increasing clockwise
- For a full circle, use start_angle=0 and end_angle=2π
Draw a quadratic Bézier curve.
Parameters
p0
(tuple[float, float]): Start point (x, y)p1
(tuple[float, float]): Control point (x, y)p2
(tuple[float, float]): End point (x, y)color
(int, tuple or color object): Color of the curve.width
(int, optional): Line width in pixels (default: 1)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Draw a cubic Bézier curve.
Parameters
p0
(tuple[float, float]): Start point (x, y)p1
(tuple[float, float]): First control point (x, y)p2
(tuple[float, float]): Second control point (x, y)p3
(tuple[float, float]): End point (x, y)color
(int, tuple or color object): Color of the curve.width
(int, optional): Line width in pixels (default: 1)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Draw a smooth spline through polygon points.
Parameters
points
(list[tuple[float, float]]): List of (x, y) coordinates to interpolate throughcolor
(int, tuple or color object): Color of the spline.width
(int, optional): Line width in pixels (default: 1)tension
(float, optional): Spline tension (0.0 = angular, 0.5 = smooth, default: 0.5)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Fill a smooth spline area through polygon points.
Parameters
points
(list[tuple[float, float]]): List of (x, y) coordinates to interpolate throughcolor
(int, tuple or color object): Fill color.tension
(float, optional): Spline tension (0.0 = angular, 0.5 = smooth, default: 0.5)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Draw text on the canvas.
Parameters
text
(str): Text to drawposition
(tuple[float, float]): Position coordinates (x, y)color
(int, tuple or color object): Text color.font
(BitmapFont, optional): Font object to use for rendering. IfNone
, uses BitmapFont.font8x8()scale
(float, optional): Text scale factor (default: 1.0)mode
(DrawMode
, optional): Drawing mode (default:DrawMode.FAST
)
Bitmap font for text rendering. Supports BDF/PCF formats, including optional gzip-compressed files (.bdf.gz, .pcf.gz).
Load a bitmap font from file.
Supports BDF (Bitmap Distribution Format) and PCF (Portable Compiled Format) files, including
optionally gzip-compressed variants (e.g., .bdf.gz
, .pcf.gz
).
Parameters
path
(str): Path to the font file
Examples
font = BitmapFont.load("unifont.bdf")
canvas.draw_text("Hello", (10, 10), font, (255, 255, 255))
Get the built-in default 8x8 bitmap font with all available characters.
This font includes ASCII, extended ASCII, Greek, and box drawing characters.
Examples
font = BitmapFont.font8x8()
canvas.draw_text("Hello World!", (10, 10), font, (255, 255, 255))
Grayscale image format (single channel, u8)
Principal Component Analysis (PCA) for dimensionality reduction.
PCA is a statistical technique that transforms data to a new coordinate system where the greatest variance lies on the first coordinate (first principal component), the second greatest variance on the second coordinate, and so on.
Examples
import zignal
import numpy as np
# Create PCA instance
pca = zignal.PCA()
# Prepare data using Matrix
data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
matrix = zignal.Matrix.from_numpy(data)
# Fit PCA, keeping 2 components
pca.fit(matrix, num_components=2)
# Project a single vector
coeffs = pca.project([2, 3, 4])
# Transform batch of data
transformed = pca.transform(matrix)
# Reconstruct from coefficients
reconstructed = pca.reconstruct(coeffs)
Fit the PCA model on training data.
Parameters
data
(Matrix): Training samples matrix (n_samples × n_features)num_components
(int, optional): Number of components to keep. If None, keeps min(n_samples-1, n_features)
Raises
- ValueError: If data has insufficient samples (< 2)
- ValueError: If num_components is 0
Examples
matrix = zignal.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
pca.fit(matrix) # Keep all possible components
pca.fit(matrix, num_components=2) # Keep only 2 components
Project a single vector onto the PCA space.
Parameters
vector
(list[float]): Input vector to project
Returns
list[float]: Coefficients in PCA space
Raises
- RuntimeError: If PCA has not been fitted
- ValueError: If vector dimension doesn't match fitted data
Examples
coeffs = pca.project([1.0, 2.0, 3.0])
Transform data matrix to PCA space.
Parameters
data
(Matrix): Data matrix (n_samples × n_features)
Returns
Matrix: Transformed data (n_samples × n_components)
Raises
- RuntimeError: If PCA has not been fitted
- ValueError: If data dimensions don't match fitted data
Examples
transformed = pca.transform(matrix)
Reconstruct a vector from PCA coefficients.
Parameters
coefficients
(List[float]): Coefficients in PCA space
Returns
List[float]: Reconstructed vector in original space
Raises
- RuntimeError: If PCA has not been fitted
- ValueError: If number of coefficients doesn't match number of components
Examples
reconstructed = pca.reconstruct([1.0, 2.0])
Feature Distribution Matching for image style transfer.
Initialize a new FeatureDistributionMatching instance.
Creates a new FDM instance that can be used to transfer color distributions between images. The instance maintains internal state for efficient batch processing of multiple images with the same target distribution.
Examples
# Create an FDM instance
fdm = FeatureDistributionMatching()
# Single image transformation
source = Image.load("portrait.png")
target = Image.load("sunset.png")
fdm.match(source, target) # source is modified in-place
source.save("portrait_sunset.png")
# Batch processing with same style
style = Image.load("style_reference.png")
fdm.set_target(style)
for filename in image_files:
img = Image.load(filename)
fdm.set_source(img)
fdm.update()
img.save(f"styled_{filename}")
Notes
- The algorithm matches mean and covariance of pixel distributions
- Target statistics are computed once and can be reused for multiple sources
- See: https://facebookresearch.github.io/dino/blog/
Set the target image whose distribution will be matched.
This method computes and stores the target distribution statistics (mean and covariance) for reuse across multiple source images. This is more efficient than recomputing the statistics for each image when applying the same style to multiple images.
Parameters
image
(Image
): Target image providing the color distribution to match. Must be RGB.
Examples
fdm = FeatureDistributionMatching()
target = Image.load("sunset.png")
fdm.set_target(target)
Set the source image to be transformed.
The source image will be modified in-place when update() is called.
Parameters
image
(Image
): Source image to be modified. Must be RGB.
Examples
fdm = FeatureDistributionMatching()
source = Image.load("portrait.png")
fdm.set_source(source)
Set both source and target images and apply the transformation.
This is a convenience method that combines set_source(), set_target(), and update() into a single call. The source image is modified in-place.
Parameters
source
(Image
): Source image to be modified (RGB)target
(Image
): Target image providing the color distribution to match (RGB)
Examples
fdm = FeatureDistributionMatching()
source = Image.load("portrait.png")
target = Image.load("sunset.png")
fdm.match(source, target) # source is now modified
source.save("portrait_sunset.png")
Apply the feature distribution matching transformation.
This method modifies the source image in-place to match the target distribution. Both source and target must be set before calling this method.
Raises
RuntimeError
: If source or target has not been set
Examples
fdm = FeatureDistributionMatching()
fdm.set_target(target)
fdm.set_source(source)
fdm.update() # source is now modified
Batch processing
fdm.set_target(style_image)
for img in images:
fdm.set_source(img)
fdm.update() # Each img is modified in-place
Result of solving an assignment problem.
Contains the optimal assignments and total cost.
Attributes
assignments
: List of column indices for each row (None if unassigned)total_cost
: Total cost of the assignment
Iterator over image pixels yielding (row, col, pixel) in native format.
This iterator walks the image in row-major order (top-left to bottom-right). For views, iteration respects the view bounds and the underlying stride, so you only traverse the visible sub-rectangle without copying.
Examples
image = Image(2, 3, Rgb(255, 0, 0), format=zignal.Rgb)
for r, c, pixel in image:
print(f"image[{r}, {c}] = {pixel}")
Notes
Returned by
iter(Image)
/Image.__iter__()
Use
Image.to_numpy()
when you need bulk numeric processing for best performance.
Rendering quality mode for drawing operations.
Attributes
FAST
(int): Fast rendering without antialiasing (value: 0)SOFT
(int): High-quality rendering with antialiasing (value: 1)
Notes
- FAST mode provides pixel-perfect rendering with sharp edges
- SOFT mode provides smooth, antialiased edges for better visual quality
- Default mode is FAST for performance
RGB color in sRGB colorspace with components in range 0-255
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
RGBA color with alpha channel, components in range 0-255
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
HSV (Hue-Saturation-Value) color representation
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
HSL (Hue-Saturation-Lightness) color representation
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
CIELAB color space representation
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
CIE 1931 XYZ color space representation
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
Oklab perceptual color space representation
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
Oklch perceptual color space in cylindrical coordinates
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
CIE LCH color space representation (cylindrical Lab)
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
LMS color space representing Long, Medium, Short wavelength cone responses
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
XYB color space used in JPEG XL image compression
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
YCbCr color space used in JPEG and video encoding
Blend with another color using the specified blend mode.
Parameters
overlay
: AnRgba
color or tuple (r, g, b, a) with values 0-255mode
:Blending
enum value (optional, defaults toBlending.NORMAL
)
Examples
base = zignal.Rgb(255, 0, 0)
overlay = zignal.Rgba(0, 255, 0, 128)
result = base.blend(overlay)
result = base.blend(overlay, mode=zignal.Blending.MULTIPLY)
Convert to a grayscale value representing the luminance/lightness as an integer between 0 and 255.
Blending modes for color composition.
Overview
These modes determine how colors are combined when blending. Each mode produces different visual effects useful for various image compositing operations.
Blend Modes
Mode | Description | Best Use Case |
---|---|---|
NORMAL | Standard alpha blending with transparency | Layering images |
MULTIPLY | Darkens by multiplying colors (white has no effect) | Shadows, darkening |
SCREEN | Lightens by inverting, multiplying, then inverting | Highlights, glow |
OVERLAY | Combines multiply and screen based on base color | Contrast enhance |
SOFT_LIGHT | Gentle contrast adjustment | Subtle lighting |
HARD_LIGHT | Like overlay but uses overlay color to determine blend | Strong contrast |
COLOR_DODGE | Brightens base color based on overlay | Bright highlights |
COLOR_BURN | Darkens base color based on overlay | Deep shadows |
DARKEN | Selects darker color per channel | Remove white |
LIGHTEN | Selects lighter color per channel | Remove black |
DIFFERENCE | Subtracts darker from lighter color | Invert/compare |
EXCLUSION | Similar to difference but with lower contrast | Soft inversion |
Examples
base = zignal.Rgb(100, 100, 100)
overlay = zignal.Rgba(200, 50, 150, 128)
# Apply different blend modes
normal = base.blend(overlay, zignal.Blending.NORMAL)
multiply = base.blend(overlay, zignal.Blending.MULTIPLY)
screen = base.blend(overlay, zignal.Blending.SCREEN)
Notes
- All blend modes respect alpha channel for proper compositing
- Result color type matches the base color type
- Overlay must be RGBA or convertible to RGBA
Interpolation methods for image resizing.
Performance and quality comparison:
Method | Quality | Speed | Best Use Case | Overshoot |
---|---|---|---|---|
NEAREST_NEIGHBOR | ★☆☆☆☆ | ★★★★★ | Pixel art, masks | No |
BILINEAR | ★★☆☆☆ | ★★★★☆ | Real-time, preview | No |
BICUBIC | ★★★☆☆ | ★★★☆☆ | General purpose | Yes |
CATMULL_ROM | ★★★★☆ | ★★★☆☆ | Natural images | No |
MITCHELL | ★★★★☆ | ★★☆☆☆ | Balanced quality | Yes |
LANCZOS | ★★★★★ | ★☆☆☆☆ | High-quality resize | Yes |
Note: "Overshoot" means the filter can create values outside the input range, which can cause ringing artifacts but may also enhance sharpness.
Motion blur effect configuration.
Use the static factory methods to create motion blur configurations:
MotionBlur.linear(angle, distance)
- Linear motion blurMotionBlur.radial_zoom(center, strength)
- Radial zoom blurMotionBlur.radial_spin(center, strength)
- Radial spin blur
Examples
import math
from zignal import Image, MotionBlur
img = Image.load("photo.jpg")
# Linear motion blur
horizontal = img.motion_blur(MotionBlur.linear(angle=0, distance=30))
vertical = img.motion_blur(MotionBlur.linear(angle=math.pi/2, distance=20))
# Radial zoom blur
zoom = img.motion_blur(MotionBlur.radial_zoom(center=(0.5, 0.5), strength=0.7))
zoom_default = img.motion_blur(MotionBlur.radial_zoom()) # Uses defaults
# Radial spin blur
spin = img.motion_blur(MotionBlur.radial_spin(center=(0.3, 0.7), strength=0.5))
Optimization policy for assignment problems.
Determines whether to minimize or maximize the total cost.