// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie // SPDX-License-Identifier: MIT //! Packs PNGs into a Windows icon file, for the icon grebe.exe carries: //! `ico OUT.ico IN.png...`. An icon file can hold each size as a PNG as it //! is, which every Windows since Vista reads, so nothing is decoded; the //! size of each comes from its PNG header. const std = @import("std"); pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; const args = try init.minimal.args.toSlice(init.arena.allocator()); if (args.len < 3) { std.log.err("usage: ico OUT.ico IN.png...", .{}); std.process.exit(2); } const cwd = std.Io.Dir.cwd(); const pngs = args[2..]; var images: std.ArrayList([]u8) = .empty; defer { for (images.items) |i| gpa.free(i); images.deinit(gpa); } for (pngs) |path| try images.append(gpa, try cwd.readFileAlloc(io, path, gpa, .limited(16 * 1024 * 1024))); var out: std.Io.Writer.Allocating = .init(gpa); defer out.deinit(); const w = &out.writer; // ICONDIR: reserved, type 1 (icon), count. try w.writeInt(u16, 0, .little); try w.writeInt(u16, 1, .little); try w.writeInt(u16, @intCast(images.items.len), .little); var offset: u32 = @intCast(6 + 16 * images.items.len); for (images.items, pngs) |png, path| { const width, const height = size(png) orelse { std.log.err("{s}: not a PNG", .{path}); std.process.exit(1); }; // ICONDIRENTRY: 256 is written as 0. try w.writeByte(@truncate(width)); try w.writeByte(@truncate(height)); try w.writeByte(0); // colors in a palette: none try w.writeByte(0); try w.writeInt(u16, 1, .little); // planes try w.writeInt(u16, 32, .little); // bits per pixel try w.writeInt(u32, @intCast(png.len), .little); try w.writeInt(u32, offset, .little); offset += @intCast(png.len); } for (images.items) |png| try w.writeAll(png); try cwd.writeFile(io, .{ .sub_path = args[1], .data = out.written() }); } /// A PNG's width and height, from its IHDR; null if it is not one, or is /// larger than an icon can say. fn size(png: []const u8) ?struct { u32, u32 } { const signature = "\x89PNG\r\n\x1a\n"; if (png.len < 24 or !std.mem.eql(u8, png[0..8], signature) or !std.mem.eql(u8, png[12..16], "IHDR")) return null; const width = std.mem.readInt(u32, png[16..20], .big); const height = std.mem.readInt(u32, png[20..24], .big); if (width == 0 or height == 0 or width > 256 or height > 256) return null; return .{ width, height }; }