Masks are grayscale images that control how pixels from different source images are weighted during blending. Proper mask design is crucial for seamless image stitching.
What is a Mask?
A mask is a single-channel (grayscale) Image where each pixel value represents the weight or contribution of the corresponding pixel in the source image:
255 (white) : Full contribution - pixel is fully included
0 (black) : No contribution - pixel is excluded
1-254 (gray) : Partial contribution - pixel is blended proportionally
Image mask;
mask.channels = GRAY_CHANNELS; // Always 1 for masks
mask.width = source_image.width;
mask.height = source_image.height;
// mask.data[i] ranges from 0 to 255
Masks must have the same dimensions as their corresponding images. The library asserts this requirement in the feed() function.
Creating Masks
Horizontal Masks
Horizontal masks fade from left to right or right to left, perfect for panoramas:
Image create_mask ( int width , int height , float range , int left , int right );
Width of the mask (must match image width)
Height of the mask (must match image height)
Fraction of width for the transition region (0.0 to 1.0)
0.1 = 10% of width will be masked
0.2 = 20% of width will be masked
Non-zero to mask the left edge (fade from black to white)
Non-zero to mask the right edge (fade from white to black)
Left Mask
Right Mask
Both Sides
// Mask for left image in panorama
// Left 10% is black (0), rest is white (255)
Image mask = create_mask (img.width, img.height, 0.1 f , 1 , 0 );
Visual representation: 0────────255═════════════255
[10% fade] [90% full]
Use for images on the left side of a panorama. // Mask for right image in panorama
// Right 10% is black (0), rest is white (255)
Image mask = create_mask (img.width, img.height, 0.1 f , 0 , 1 );
Visual representation: 255═════════════255────────0
[90% full] [10% fade]
Use for images on the right side of a panorama. // Mask for middle image in panorama
// Both edges fade out
Image mask = create_mask (img.width, img.height, 0.1 f , 1 , 1 );
Visual representation: 0────255═════════════255────0
[10%] [80% full] [10%]
Use for images in the middle of a multi-image panorama.
Implementation Details
The create_mask function creates hard-edged masks:
Image create_mask ( int width , int height , float range , int left , int right ) {
unsigned char * grayBuffer = ( unsigned char * ) malloc (width * height);
memset (grayBuffer, 255 , width * height); // Start with all white
int cut = ( int )(range * width); // Calculate transition width
if (left) {
// Mask left edge
for ( int i = 0 ; i < cut; ++ i) {
for ( int j = 0 ; j < height; ++ j) {
grayBuffer [i + width * j] = 0 ;
}
}
}
if (right) {
// Mask right edge
for ( int i = 0 ; i < cut; ++ i) {
for ( int j = 0 ; j < height; ++ j) {
grayBuffer [(width - i - 1 ) + width * j] = 0 ;
}
}
}
// ... set result fields
}
Vertical Masks
Vertical masks fade from top to bottom or bottom to top:
Image create_vertical_mask ( int width , int height , float range , int top , int bottom );
Non-zero to mask the top edge
Non-zero to mask the bottom edge
// Mask top 15% for top image in vertical stitch
Image mask = create_vertical_mask (img.width, img.height, 0.15 f , 1 , 0 );
The distance transform creates smooth gradients from hard-edged masks, resulting in better blending transitions.
How It Works
Distance transform computes the distance from each pixel to the nearest masked (zero-valued) pixel:
Initialize
Convert binary mask to distance map:
Pixels with value > 0 become 0.0
Pixels with value 0 become 255.0
Forward Pass
Scan left-to-right, top-to-bottom, updating each pixel with the minimum distance to its neighbors: const float mask5 [] = { 1.0 f , 1.4 f }; // Orthogonal and diagonal distances
min_val = fminf (current, get_pixel (x - 1 , y) + mask5 [ 0 ]); // Left
min_val = fminf (min_val, get_pixel (x, y - 1 ) + mask5 [ 0 ]); // Top
min_val = fminf (min_val, get_pixel (x - 1 , y - 1 ) + mask5 [ 1 ]); // Top-left
min_val = fminf (min_val, get_pixel (x + 1 , y - 1 ) + mask5 [ 1 ]); // Top-right
Backward Pass
Scan right-to-left, bottom-to-top, refining distances: min_val = fminf (current, get_pixel (x + 1 , y) + mask5 [ 0 ]); // Right
min_val = fminf (min_val, get_pixel (x, y + 1 ) + mask5 [ 0 ]); // Bottom
min_val = fminf (min_val, get_pixel (x + 1 , y + 1 ) + mask5 [ 1 ]); // Bottom-right
min_val = fminf (min_val, get_pixel (x - 1 , y + 1 ) + mask5 [ 1 ]); // Bottom-left
Apply
Replace original mask values with computed distances (but preserve original white regions).
Feather Blending
Manual Application
Distance transform is particularly important for feather blending: Blender * blender = create_blender (FEATHER, out_size, - 1 );
// Enable distance transform
blender -> do_distance_transform = 1 ;
// Now masks will be smoothed automatically during feed()
feed (blender, & img , & mask , tl);
For feather blending, always consider enabling distance transform to create smooth transitions.
Apply distance transform directly to a mask: Image mask = create_mask (width, height, 0.1 f , 0 , 1 );
// Apply distance transform in-place
distance_transform ( & mask );
// Mask now has smooth gradients
Mask must be single-channel (GRAY_CHANNELS). The function asserts this requirement.
Before (Hard Edge)
After (Smooth Gradient)
Mask values: [ 0 , 0 , 0 , 255 , 255 , 255 , 255 , 255 ]
└─────┘ └───────────────────┘
Cut Full region
Mask Usage in Blending
Multiband Blending
Multiband blending uses masks at multiple scales:
// 1. Convert mask to ImageS
ImageS mask_img_ = create_empty_image_s (mask_img -> width ,
mask_img -> height ,
mask_img -> channels );
convert_image_to_image_s (mask_img, & mask_img_ );
// 2. Build Gaussian pyramid of masks
for ( int j = 0 ; j < num_bands; ++ j) {
mask_gaussian [j] = mask_img_;
mask_img_ = downsample_s ( & mask_img_); // Half resolution
}
// 3. Blend each pyramid level with corresponding mask
float maskVal = mask_gaussian [level]. data [maskIndex] * ( 1.0 / 255.0 );
float imgVal = img_laplacians [level]. data [imgIndex] * maskVal;
out [level]. data [outLevelIndex] += imgVal;
Multiband blending automatically downsamples masks to match each pyramid level, creating a Gaussian mask pyramid.
Feather Blending
Feather blending uses masks at full resolution:
float weight = mask_img -> data [mask_pos] / 255.0 f ;
out_mask -> data [dst_mask] += weight;
for ( int channel = 0 ; channel < RGB_CHANNELS; channel ++ ) {
out -> data [dst_idx + channel] += img -> data [image_pos + channel] * weight;
}
The final pixel value is the weighted average:
final_out [level]. data [imgIndex] =
( short )( out [level]. data [imgIndex] / ( out_mask [level]. data [maskIndex] + WEIGHT_EPS));
Practical Examples
Two-Image Panorama
// Load images
Image img1 = create_image ( "left.jpeg" );
Image img2 = create_image ( "right.jpeg" );
// Create complementary masks with 10% overlap
Image mask1 = create_mask (img1.width, img1.height, 0.1 f , 0 , 1 ); // Fade right
Image mask2 = create_mask (img2.width, img2.height, 0.1 f , 1 , 0 ); // Fade left
// Setup output canvas
int overlap = (img1.width * 0.1 f );
int out_width = (img1.width * 2 ) - (overlap * 2 );
StitchRect out_size = { 0 , 0 , out_width, img1 . height };
// Create blender
Blender * b = create_blender (MULTIBAND, out_size, 5 );
// Feed images at appropriate positions
StitchPoint pt1 = { 0 , 0 };
feed (b, & img1 , & mask1 , pt1);
StitchPoint pt2 = { img1 . width - overlap * 2 , 0 };
feed (b, & img2 , & mask2 , pt2);
// Blend and save
blend (b);
save_image ( & b -> result , "panorama.jpg" );
// Cleanup
destroy_blender (b);
destroy_image ( & img1 );
destroy_image ( & img2 );
destroy_image ( & mask1 );
destroy_image ( & mask2 );
Custom Mask Creation
For advanced use cases, create custom masks programmatically:
// Create circular mask
Image create_circular_mask ( int width , int height , int cx , int cy , int radius ) {
Image mask = create_empty_image (width, height, GRAY_CHANNELS);
for ( int y = 0 ; y < height; y ++ ) {
for ( int x = 0 ; x < width; x ++ ) {
int dx = x - cx;
int dy = y - cy;
float distance = sqrt (dx * dx + dy * dy);
if (distance < radius) {
mask . data [y * width + x] = 255 ; // Inside circle
} else if (distance < radius + 10 ) {
// Fade region
float t = (distance - radius) / 10.0 f ;
mask . data [y * width + x] = ( unsigned char )( 255 * ( 1.0 f - t));
} else {
mask . data [y * width + x] = 0 ; // Outside circle
}
}
}
return mask;
}
Best Practices
Recommendation: 10-20% overlap for most panoramas
Too small (less than 5%): Visible seams, artifacts
Optimal (10-20%): Smooth transitions
Too large (more than 30%): Wasted computation, potential ghosting
float range = 0.15 f ; // 15% is a safe default
Image mask = create_mask (width, height, range, 0 , 1 );
For best results, adjacent images should have complementary masks: // Image 1: fade out right edge
Image mask1 = create_mask (w, h, 0.1 f , 0 , 1 );
// Image 2: fade out left edge (symmetric)
Image mask2 = create_mask (w, h, 0.1 f , 1 , 0 );
The overlapping region should sum to approximately 1.0 (255) when both masks overlap.
Distance Transform Trade-offs
Masks are full-resolution images and consume memory: Memory per mask = width × height × 1 byte
Always destroy masks after feeding to the blender: feed (b, & img , & mask , pt);
destroy_image ( & mask ); // Can free immediately
Troubleshooting
Assertion Failed Assertion failed: img->height == mask_img->height &&
img->width == mask_img->width
Solution: Ensure mask dimensions exactly match image dimensions.Image mask = create_mask (img.width, img.height, 0.1 f , 0 , 1 );
Visible Seams Seams are visible in the output image. Solutions:
Increase overlap region: range = 0.2f
Enable distance transform for feather blending
Switch to multiband blending
Check mask symmetry
Black Regions Output has unexpected black areas. Causes:
Mask regions with zero values that don’t overlap other images
Incorrect positioning (gaps between images)
Solution: Verify mask coverage and image positions.
Ghosting Double images or transparency effects. Causes:
Overlapping regions too large
Moving objects in the scene
Poor image alignment
Solution: Reduce overlap or align images better before stitching.
Next Steps
Blending Learn how masks are used in multiband and feather blending
Image Types Understand the Image type used for masks