App Icon Generator — Zig source
Turn one image into the full web app icon set — favicon, apple-touch-icon and manifest sizes as crisp PNGs. Letterboxed contain-fit, optional white background — runs entirely in your browser.
This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.
//! App Icon Generator — contain-fit, centered export geometry for the
//! 16/32/180/192/512 icon set (Zig 0.13, stdlib only). Port of the authored
//! TypeScript snippet src/tool-sources/app-icon-generator/typescript.ts.
const std = @import("std");
/// One export target: the square canvas edge and the centered contain-fit
/// rect the source image is drawn into.
pub const IconRect = struct {
size: u16, // canvas edge (px x px)
x: i32, y: i32, // top-left offset: centers the fitted image
w: u32, h: u32, // fitted image size, at least 1x1
};
pub const default_sizes = [_]u16{ 16, 32, 180, 192, 512 };
/// The deterministic half of the tool: per export size, the contain-fit rect
/// (scale = min(px/w, px/h), clamped to >= 1) plus the centered draw offset —
/// the geometry the TS pipeline feeds ctx.drawImage before canvas.toBlob
/// PNG-encodes each square. Bitmap decode and PNG encode stay with the host.
/// @round rounds half away from zero, matching JS Math.round; all inputs are
/// positive so @intFromFloat can never trap. Caller provides `out` (at least
/// sizes.len entries); the filled slice is returned.
pub fn iconSetPlan(src_w: u32, src_h: u32, sizes: []const u16, out: []IconRect) []IconRect {
for (sizes, 0..) |px, i| {
const scale = @min(
@as(f64, @floatFromInt(px)) / @as(f64, @floatFromInt(src_w)),
@as(f64, @floatFromInt(px)) / @as(f64, @floatFromInt(src_h)),
);
const w: u32 = @intFromFloat(@max(1, @round(@as(f64, @floatFromInt(src_w)) * scale)));
const h: u32 = @intFromFloat(@max(1, @round(@as(f64, @floatFromInt(src_h)) * scale)));
const x: f64 = @as(f64, @floatFromInt(px)) - @as(f64, @floatFromInt(w)); // letterbox
const y: f64 = @as(f64, @floatFromInt(px)) - @as(f64, @floatFromInt(h));
out[i] = .{
.size = px,
.x = @intFromFloat(@round(x / 2)),
.y = @intFromFloat(@round(y / 2)),
.w = w,
.h = h,
};
}
return out[0..sizes.len];
}
test "contain-fit is centered and clamped at 1" {
var buf: [default_sizes.len]IconRect = undefined;
// Portrait 3x5 into 16px: scale = 16/5 = 3.2, h = 16, w = round(9.6) = 10,
// centered x = round(6/2) = 3.
const portrait = iconSetPlan(3, 5, &default_sizes, &buf);
try std.testing.expectEqual(@as(usize, 5), portrait.len);
try std.testing.expectEqual(IconRect{ .size = 16, .x = 3, .y = 0, .w = 10, .h = 16 }, portrait[0]);
// Extreme 1000x1 banner into 16px: the long side is exact (16), the thin
// side rounds to 0.016 -> 0 and must clamp to 1, centered at y = 8.
const banner = iconSetPlan(1000, 1, default_sizes[0..1], buf[0..1]);
try std.testing.expectEqual(IconRect{ .size = 16, .x = 0, .y = 8, .w = 16, .h = 1 }, banner[0]);
}
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 →