Skip to content

Image Splitter — Zig source

Split an image into an even grid of tiles — rows × columns with a live cut-line preview and per-tile or bulk PNG download. Runs entirely in your browser.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Image splitter — tile geometry — Zig port of the image-splitter tool.

const std = @import("std");

/// One cut of the grid: the source rectangle that becomes a tile. In the
/// browser tool this rect is drawImage'd into its own canvas and encoded
/// to PNG; the geometry here is the deterministic part of that pipeline.
const TileRect = struct {
    x: i32,
    y: i32,
    width: i32,
    height: i32,

    fn format(
        self: TileRect,
        comptime _: []const u8,
        _: std.fmt.FormatOptions,
        writer: anytype,
    ) !void {
        try writer.print("src=({d}, {d}) {d}x{d}", .{
            self.x, self.y, self.width, self.height,
        });
    }
};

/// Split a width x height image into rows x cols tiles, row-major.
/// Mirrors splitToTiles in typescript.ts: tileW = floor(width / cols),
/// tileH = floor(height / rows) — Zig integer division truncates toward
/// zero, which IS Math.floor for positives, so a 1024px width over 3 cols
/// yields 341px tiles and the last 1px column is dropped, exactly like the
/// TS. Caller owns the returned slice.
fn splitTiles(
    allocator: std.mem.Allocator,
    width: i32,
    height: i32,
    rows: i32,
    cols: i32,
) ![]TileRect {
    const tile_w = @divTrunc(width, cols);
    const tile_h = @divTrunc(height, rows);
    var tiles = std.ArrayList(TileRect).init(allocator);
    errdefer tiles.deinit();
    var row: i32 = 0;
    while (row < rows) : (row += 1) {
        var col: i32 = 0;
        while (col < cols) : (col += 1) {
            try tiles.append(.{
                .x = col * tile_w,
                .y = row * tile_h,
                .width = tile_w,
                .height = tile_h,
            });
        }
    }
    return tiles.toOwnedSlice();
}

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    // 1024x768 image, 2 rows x 3 cols -> six 341x384 tiles.
    const tiles = try splitTiles(arena.allocator(), 1024, 768, 2, 3);
    for (tiles) |tile| {
        try std.io.getStdOut().writer().print("{f}\n", .{tile});
    }
}

Also available in 8 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →