Skip to main content
Stitcher uses three different image structures optimized for different stages of the processing pipeline. Each type stores pixel data with different precision and range characteristics.

Type Overview

All three image types share a common structure but differ in their data type:
image_operations.h
typedef struct {
    unsigned char *data;
    int width;
    int height;
    int channels;
} Image;
Standard 8-bit image representation. Each pixel component is stored as an unsigned byte (0-255).Use for:
  • Input images from JPEG files
  • Final output images
  • Masks
  • I/O operations

Common Properties

All image types share these fields:
data
pointer
Pointer to the pixel data array. Data is stored in row-major order with interleaved channels.Memory layout for RGB:
[R0, G0, B0, R1, G1, B1, ..., Rn, Gn, Bn]
Accessing pixel (x, y), channel c:
int index = (y * width + x) * channels + c;
value = data[index];
width
int
Image width in pixels.
height
int
Image height in pixels.
channels
int
Number of channels per pixel:
  • RGB_CHANNELS (3) for color images
  • GRAY_CHANNELS (1) for grayscale/masks

Image Type Constants

image_operations.h
#define RGB_CHANNELS 3
#define GRAY_CHANNELS 1
The ImageType enum is used internally for type dispatch:
image_operations.h
typedef enum {
    IMAGE,
    IMAGES,
    IMAGEF
} ImageType;

Creating Images

Empty Images

Create uninitialized images for output buffers:
Example
// Create 8-bit RGB image
Image img = create_empty_image(1920, 1080, RGB_CHANNELS);

// Create float accumulation buffer
ImageF acc = create_empty_image_f(1920, 1080, RGB_CHANNELS);

// Create short pyramid level
ImageS pyramid = create_empty_image_s(960, 540, RGB_CHANNELS);
create_empty_image
function
Image create_empty_image(int width, int height, int channels);
Creates an 8-bit image with allocated but uninitialized data.
create_empty_image_f
function
ImageF create_empty_image_f(int width, int height, int channels);
Creates a float image with allocated but uninitialized data.
create_empty_image_s
function
ImageS create_empty_image_s(int width, int height, int channels);
Creates a short image with allocated but uninitialized data.

From Files

Loading Images
// Load JPEG image
Image img = create_image("photo.jpeg");
if (img.data == NULL) {
    fprintf(stderr, "Failed to load image\n");
    return;
}

// Use the image...

// Clean up
destroy_image(&img);

Type Conversions

Stitcher provides conversion functions between image types:
Image input = create_image("photo.jpg");
ImageF output = create_empty_image_f(input.width, input.height, input.channels);
convert_image_to_image_f(&input, &output);
// output.data[i] = (float)input.data[i]
All conversions clamp values to the valid range [0, 255] for the output type. The clamp function ensures no overflow or underflow occurs.

Memory Management

Calculating Image Size

Size Functions
// Get total number of elements (width × height × channels)
int size = image_size(&img);          // For Image
int size_s = image_size_s(&img_s);    // For ImageS
int size_f = image_size_f(&img_f);    // For ImageF

// Calculate memory usage
size_t bytes = size * sizeof(unsigned char);  // Image
size_t bytes_s = size_s * sizeof(short);      // ImageS
size_t bytes_f = size_f * sizeof(float);      // ImageF

Destroying Images

Always destroy images when finished to prevent memory leaks. Each create_* or load_* function allocates memory that must be freed.
Cleanup
Image img = create_image("input.jpg");
ImageF img_f = create_empty_image_f(100, 100, 3);
ImageS img_s = create_empty_image_s(100, 100, 3);

// Use images...

// Clean up
destroy_image(&img);
destroy_image_f(&img_f);
destroy_image_s(&img_s);
destroy_image
function
void destroy_image(Image *img);
Frees the data buffer and resets the image structure.
destroy_image_f
function
void destroy_image_f(ImageF *img);
Frees the float data buffer.
destroy_image_s
function
void destroy_image_s(ImageS *img);
Frees the short data buffer.

Choosing the Right Type

  • Reading images from files (JPEG, PNG)
  • Writing final output images
  • Creating masks for blending
  • Working with standard 8-bit color data
  • Minimizing memory usage for final results
Memory: W × H × C × 1 byte
  • Accumulating weighted sums (blending)
  • Performing multiple arithmetic operations
  • Requiring high precision
  • Storing normalized weights (0.0 to 1.0)
  • Avoiding quantization artifacts
Memory: W × H × C × 4 bytes
  • Computing Laplacian pyramids (needs negative values)
  • Storing intermediate pyramid levels
  • Upsampling/downsampling operations
  • Needing signed values with reasonable precision
  • Balancing memory vs. precision
Memory: W × H × C × 2 bytes

Image Processing Pipeline Example

A typical multiband blending pipeline uses all three types:
Complete Pipeline
// 1. Load input as Image (8-bit)
Image input = create_image("photo.jpg");
Image mask = create_mask(input.width, input.height, 0.1f, 0, 1);

// 2. Convert to ImageS for pyramid processing
ImageS input_s = create_empty_image_s(input.width, input.height, RGB_CHANNELS);
convert_image_to_image_s(&input, &input_s);

// 3. Build Laplacian pyramid (ImageS supports negative values)
ImageS downsampled = downsample_s(&input_s);
ImageS upsampled = upsample_image_s(&downsampled, 4.0f);
// Laplacian = original - upsampled (can be negative)
for (int i = 0; i < image_size_s(&input_s); i++) {
    upsampled.data[i] = input_s.data[i] - upsampled.data[i];
}

// 4. Accumulate in ImageF for precision
ImageF accumulated = create_empty_image_f(input.width, input.height, RGB_CHANNELS);
for (int i = 0; i < image_size_f(&accumulated); i++) {
    accumulated.data[i] += upsampled.data[i] * weight;
}

// 5. Normalize and convert to ImageS
ImageS normalized = create_empty_image_s(accumulated.width, 
                                         accumulated.height, 
                                         RGB_CHANNELS);
for (int i = 0; i < image_size_s(&normalized); i++) {
    normalized.data[i] = (short)(accumulated.data[i] / total_weight);
}

// 6. Convert final result back to Image for output
Image output = create_empty_image(normalized.width, 
                                  normalized.height, 
                                  RGB_CHANNELS);
convert_images_to_image(&normalized, &output);

// 7. Save result
save_image(&output, "result.jpg");

// 8. Cleanup
destroy_image(&input);
destroy_image(&mask);
destroy_image_s(&input_s);
destroy_image_s(&downsampled);
destroy_image_s(&upsampled);
destroy_image_f(&accumulated);
destroy_image_s(&normalized);
destroy_image(&output);

Performance Tips

Memory Access

Access pixels sequentially (row by row) for better cache performance. The data is stored in row-major order.

Type Selection

Use the smallest type that provides sufficient precision:
  • Image: Display and I/O
  • ImageS: Intermediate processing
  • ImageF: Accumulation only

Avoid Conversions

Minimize type conversions in tight loops. Convert once at the beginning and end of processing stages.

Parallel Processing

The library uses pthread for parallel operations on all image types. Ensure sufficient CPU cores are available.

Next Steps

Blending

Learn how different image types are used in blending algorithms

Masks

Understand mask creation using the Image type

Build docs developers (and LLMs) love