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:
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
typedef struct {
float * data;
int width;
int height;
int channels;
} ImageF;
Floating-point image for high-precision calculations. Each pixel component is a 32-bit float. Use for:
Accumulating weighted pixel values during blending
Intermediate computations requiring precision
Gaussian filtering operations
Avoiding quantization artifacts
typedef struct {
short * data;
int width;
int height;
int channels;
} ImageS;
Signed 16-bit integer image. Each pixel component is a short (-32,768 to 32,767). Use for:
Laplacian pyramid computations (can be negative)
Upsampling and downsampling operations
Intermediate pyramid representations
Memory-efficient storage compared to float
Common Properties
All image types share these fields:
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];
Number of channels per pixel:
RGB_CHANNELS (3) for color images
GRAY_CHANNELS (1) for grayscale/masks
Image Type Constants
#define RGB_CHANNELS 3
#define GRAY_CHANNELS 1
The ImageType enum is used internally for type dispatch:
typedef enum {
IMAGE,
IMAGES,
IMAGEF
} ImageType;
Creating Images
Empty Images
Create uninitialized images for output buffers:
// 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);
Image create_empty_image ( int width , int height , int channels );
Creates an 8-bit image with allocated but uninitialized data.
ImageF create_empty_image_f ( int width , int height , int channels );
Creates a float image with allocated but uninitialized data.
ImageS create_empty_image_s ( int width , int height , int channels );
Creates a short image with allocated but uninitialized data.
From Files
// 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 to ImageF
Image to ImageS
ImageS to Image
ImageF to Image
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
// 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.
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 );
void destroy_image (Image * img );
Frees the data buffer and resets the image structure.
void destroy_image_f (ImageF * img );
Frees the float data buffer.
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:
// 1. Load input as Image (8-bit)
Image input = create_image ( "photo.jpg" );
Image mask = create_mask (input.width, input.height, 0.1 f , 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.0 f );
// 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 );
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