Skip to content

Image Compressor — Zig source

Shrink image file sizes entirely in your browser — fixed quality or an automatic fit to a size budget, optional max-dimension resize, batch support. Nothing uploads.

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

//! Image compressor: quality search to a size budget — Zig port of the
//! image-compressor tool. Zig's stdlib has no image codec, so the encoder
//! is a stand-in sized like a real one (libwebp / stb_image_write plug in
//! here). Quality runs in integer hundredths (5..95, step 5) so the search
//! has no float-epsilon edges — 0.05..0.95 step 0.05 in the TS original.

const std = @import("std");

/// Stand-in for canvas -> WebP/JPEG encode: encoded size at a quality
/// given in hundredths (43 = the TS 0.43).
const EncodeSize = *const fn (quality: u32) usize;

/// Binary-search the quality until the encoded image fits the budget
/// (target_kb * 1024). Returns the best fitting quality in hundredths, or
/// null when nothing fits — the TS `best ?? encode(0.05)` fallback then
/// re-encodes once at the floor.
fn compressToBudget(encodeSize: EncodeSize, target_kb: usize) ?u32 {
    const budget = target_kb * 1024;
    var lo: u32 = 5;
    var hi: u32 = 95;
    var best: ?u32 = null;
    while (lo <= hi) {
        const mid = lo + (hi - lo) / 2; // floor — no float midpoints
        if (encodeSize(mid) <= budget) {
            best = mid; // fits — try a higher quality
            lo = mid + 5;
        } else {
            hi = mid - 5; // too big — step down (mid >= lo >= 5: no underflow)
        }
    }
    return best;
}

/// Deterministic stand-in codec: size grows ~linearly with quality.
fn fakeEncode(q: u32) usize {
    return 12_000 + @as(usize, 88_000) * q / 100; // ~100 KB at q = 100
}

pub fn main() void {
    // The live tool decodes the bitmap, draws it to a canvas, then searches.
    // 50 KB budget: a mid-range quality fits just under it.
    if (compressToBudget(fakeEncode, 50)) |q| {
        std.debug.print("best quality {d}.{d:0>2} -> {d} bytes\n", .{ q / 100, q % 100, fakeEncode(q) });
    } else {
        std.debug.print("no quality fits — falling back to the 0.05 floor\n", .{});
    }
}

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 →