API Reference
mimg can be used as a Zig library for programmatic image processing. This guide covers the complete API for integrating mimg into your Zig applications.
Setup
Add mimg as a dependency in your build.zig.zon:
.dependencies = .{
.mimg = .{
.url = "https://github.com/brian-sinquin/mimg/archive/main.tar.gz",
.hash = "...", // Run `zig fetch` to get the hash
},
},
In your build.zig:
const mimg_dep = b.dependency("mimg", .{});
exe.root_module.addImport("mimg", mimg_dep.module("mimg"));
Basic Usage
Loading and Processing Images
const std = @import("std");
const img = @import("zigimg");
const mimg = @import("mimg");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Create processing context
var ctx = mimg.types.Context.init(allocator);
defer ctx.deinit();
// Load image
const image = try img.Image.fromFilePath(allocator, "input.png");
defer image.deinit(allocator);
ctx.setImage(image);
// Apply processing
try mimg.basic.adjustBrightness(&ctx, .{20});
try mimg.basic.adjustContrast(&ctx, .{1.2});
// Save result
try ctx.image.writeToFilePath("output.png", .{});
}
Context Management
The Context struct manages image state and temporary buffers:
var ctx = mimg.types.Context.init(allocator);
defer ctx.deinit();
// Context automatically:
// - Manages image buffer
// - Reuses temporary buffers for filters
// - Handles memory allocation/deallocation
// - Provides error handling
Context Methods
// Set the image to process
ctx.setImage(loaded_image);
// Get current image dimensions
const width = ctx.image.width;
const height = ctx.image.height;
// Check if image is loaded
if (ctx.image.pixels != null) {
// Image is ready for processing
}
Available Functions
All processing functions follow the pattern:
functionName(context: *Context, args: anytype) !void
Color Adjustments
// Brightness adjustment (-255 to 255)
try mimg.basic.adjustBrightness(&ctx, .{50});
// Contrast adjustment (0.0 to 3.0)
try mimg.basic.adjustContrast(&ctx, .{1.5});
// Saturation adjustment (0.0 to 3.0)
try mimg.basic.adjustSaturation(&ctx, .{0.8});
// Gamma correction (0.1 to 3.0)
try mimg.basic.adjustGamma(&ctx, .{1.2});
// Hue rotation (0 to 360 degrees)
try mimg.basic.hueShiftImage(&ctx, .{45});
// Exposure adjustment (-2.0 to 2.0 stops)
try mimg.basic.adjustExposure(&ctx, .{0.5});
// Vibrance enhancement (0.0 to 1.0)
try mimg.basic.adjustVibrance(&ctx, .{0.3});
// Histogram equalization
try mimg.basic.equalizeImage(&ctx, .{});
Color Effects
// Convert to grayscale
try mimg.basic.grayscaleImage(&ctx, .{});
// Invert all colors
try mimg.basic.invertColors(&ctx, .{});
// Apply sepia tone
try mimg.basic.applySepia(&ctx, .{});
// Binary threshold (0 to 255)
try mimg.basic.thresholdImage(&ctx, .{128});
// Solarization effect (0 to 255)
try mimg.basic.solarizeImage(&ctx, .{180});
// Posterize levels (2 to 16)
try mimg.basic.posterizeImage(&ctx, .{8});
// Color tint using hex color and intensity
try mimg.basic.colorizeImage(&ctx, .{"#FF8000", 0.3});
// Duotone gradient using hex colors (dark to light)
try mimg.basic.duotoneImage(&ctx, .{"#003264", "#FFE096"});
Filters
// Gaussian blur (sigma 0.5 to 5.0)
try mimg.filters.gaussianBlurImage(&ctx, .{1.5});
// Box blur (kernel size 3, 5, 7, 9...)
try mimg.filters.boxBlurImage(&ctx, .{5});
// Sharpen image
try mimg.filters.sharpenImage(&ctx, .{});
// Edge detection (Sobel)
try mimg.filters.edgeDetectImage(&ctx, .{});
// Emboss effect
try mimg.filters.embossImage(&ctx, .{});
// Median filter for noise (kernel size 3, 5, 7)
try mimg.filters.medianFilterImage(&ctx, .{3});
// Add noise (0.0 to 1.0)
try mimg.filters.noiseImage(&ctx, .{0.1});
// Vignette effect (0.0 to 1.0)
try mimg.filters.vignetteImage(&ctx, .{0.5});
// Pixelation (2 to 50)
try mimg.filters.pixelateImage(&ctx, .{8});
// Oil painting effect (1 to 5)
try mimg.filters.oilPaintingImage(&ctx, .{3});
Geometric Transforms
// Resize image (1 to 65535)
try mimg.transforms.resizeImage(&ctx, .{1024, 768});
// Crop rectangle (x, y, width, height)
try mimg.transforms.cropImage(&ctx, .{100, 50, 800, 600});
// Rotate (90, 180, 270 degrees)
try mimg.transforms.rotateImage(&ctx, .{90});
// Flip horizontally
try mimg.transforms.flipImage(&ctx, .{"horizontal"});
// Flip vertically
try mimg.transforms.flipImage(&ctx, .{"vertical"});
Advanced Usage
Chaining Operations
// Chain multiple operations
try mimg.basic.adjustBrightness(&ctx, .{30});
try mimg.basic.adjustContrast(&ctx, .{1.2});
try mimg.filters.gaussianBlurImage(&ctx, .{0.8});
try mimg.filters.sharpenImage(&ctx, .{});
Batch Processing
const std = @import("std");
const img = @import("zigimg");
const mimg = @import("mimg");
pub fn processBatch(allocator: std.mem.Allocator, file_paths: []const []const u8) !void {
var ctx = mimg.types.Context.init(allocator);
defer ctx.deinit();
for (file_paths) |file_path| {
// Load image
const image = try img.Image.fromFilePath(allocator, file_path);
defer image.deinit(allocator);
ctx.setImage(image);
// Apply processing
try mimg.basic.adjustBrightness(&ctx, .{20});
try mimg.basic.adjustContrast(&ctx, .{1.1});
// Generate output path
const output_path = try std.fmt.allocPrint(allocator, "output/{s}", .{std.fs.path.basename(file_path)});
defer allocator.free(output_path);
// Save result
try ctx.image.writeToFilePath(output_path, .{});
}
}
Custom Processing Pipeline
const ProcessingPipeline = struct {
ctx: mimg.types.Context,
pub fn init(allocator: std.mem.Allocator) ProcessingPipeline {
return .{ .ctx = mimg.types.Context.init(allocator) };
}
pub fn deinit(self: *ProcessingPipeline) void {
self.ctx.deinit();
}
pub fn applyPreset(self: *ProcessingPipeline, preset_name: []const u8) !void {
if (std.mem.eql(u8, preset_name, "vintage")) {
try mimg.basic.applySepia(&self.ctx, .{});
try mimg.filters.vignetteImage(&self.ctx, .{0.4});
try mimg.basic.adjustContrast(&self.ctx, .{1.1});
} else if (std.mem.eql(u8, preset_name, "bw")) {
try mimg.basic.grayscaleImage(&self.ctx, .{});
try mimg.basic.adjustContrast(&self.ctx, .{1.3});
}
}
};
Error Handling
mimg uses Zig’s error union types for robust error handling:
const result = mimg.basic.adjustBrightness(&ctx, .{300});
if (result) {
std.debug.print("Success!\n", .{});
} else |err| switch (err) {
error.InvalidParameters => std.debug.print("Brightness value out of range\n", .{}),
error.OutOfMemory => std.debug.print("Not enough memory\n", .{}),
error.ImageTooLarge => std.debug.print("Image exceeds size limits\n", .{}),
else => std.debug.print("Processing error: {}\n", .{err}),
}
Common Error Types
InvalidParameters- Parameter values outside allowed rangesOutOfMemory- Insufficient memory for operationImageTooLarge- Image exceeds 65535×65535 or 50M pixelsUnsupportedOperation- Operation not supported for current image formatFileSystemError- File I/O issues
Memory Management
Automatic Buffer Management
var ctx = mimg.types.Context.init(allocator);
defer ctx.deinit();
// Temporary buffers are automatically:
// - Allocated when needed
// - Reused across operations
// - Freed when context is deinitialized
Memory-Efficient Processing
// For large images, consider processing in sections
const max_dimension = 2048;
if (ctx.image.width > max_dimension or ctx.image.height > max_dimension) {
// Resize before applying memory-intensive filters
try mimg.transforms.resizeImage(&ctx, .{max_dimension, max_dimension});
}
try mimg.filters.medianFilterImage(&ctx, .{5}); // Now uses less memory
Type Definitions
Context Structure
pub const Context = struct {
allocator: std.mem.Allocator,
image: img.Image,
temp_buffer: ?[]u8, // Reused for filters
pub fn init(allocator: std.mem.Allocator) Context
pub fn deinit(self: *Context) void
pub fn setImage(self: *Context, image: img.Image) void
};
Function Signatures
All processing functions use this pattern:
pub fn functionName(ctx: *Context, args: anytype) !void
Where args is a tuple/struct containing the function parameters.
Integration Examples
Web Server Integration
// Process uploaded image
pub fn processUpload(allocator: std.mem.Allocator, image_data: []const u8) ![]u8 {
var ctx = mimg.types.Context.init(allocator);
defer ctx.deinit();
// Load from memory
const image = try img.Image.fromMemory(allocator, image_data);
defer image.deinit(allocator);
ctx.setImage(image);
// Apply processing
try mimg.basic.adjustBrightness(&ctx, .{20});
try mimg.filters.gaussianBlurImage(&ctx, .{0.5});
// Return processed image data
return try ctx.image.toPng(allocator);
}
Image Processing Library
pub const ImageProcessor = struct {
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) ImageProcessor {
return .{ .allocator = allocator };
}
pub fn enhancePhoto(self: ImageProcessor, image_path: []const u8) !void {
var ctx = mimg.types.Context.init(self.allocator);
defer ctx.deinit();
const image = try img.Image.fromFilePath(self.allocator, image_path);
defer image.deinit(self.allocator);
ctx.setImage(image);
// Professional enhancement pipeline
try mimg.basic.adjustExposure(&self, .{0.3});
try mimg.basic.adjustContrast(&self, .{1.1});
try mimg.basic.adjustVibrance(&self, .{0.2});
try mimg.filters.sharpenImage(&self, .{});
const output_path = try std.fmt.allocPrint(self.allocator, "enhanced_{s}", .{std.fs.path.basename(image_path)});
defer self.allocator.free(output_path);
try ctx.image.writeToFilePath(output_path, .{});
}
};
Performance Considerations
SIMD Optimization
mimg automatically uses SIMD instructions when available:
- Color operations use
@Vector(4, f32)for 4-pixel parallel processing - Integer operations use
@Vector(4, u8)for byte-level parallelism - Performance scales with CPU SIMD capabilities
Memory Efficiency
- Buffer reuse minimizes allocations
- Tiled processing for large images (> 2048×2048)
- Automatic cleanup prevents memory leaks
Benchmarking
// Time your operations
const start = std.time.nanoTimestamp();
// ... processing ...
const end = std.time.nanoTimestamp();
const duration_ms = @intToFloat(f64, end - start) / 1_000_000.0;
std.debug.print("Processing took: {d:.2}ms\n", .{duration_ms});
Next Steps
- Download - Get mimg for your platform
- Getting Started - Learn the basics
- Troubleshooting - Debug library usage issues