const std = @import("std"); const Io = std.Io; const STBImage = @import("stb_image"); // https://en.wikipedia.org/wiki/ICO_(file_format)#Icon_file_structure pub fn main(init: std.process.Init) !void { const io = init.io; const gpa = init.gpa; const arena = init.arena.allocator(); const args = try init.minimal.args.toSlice(arena); if (args.len != 3) { std.debug.print("Usage: ./png-to-icon ", .{}); std.process.exit(1); } const cwd = Io.Dir.cwd(); const input_png_path = args[1]; const output_ico_path = args[2]; const input_png_data = try cwd.readFileAlloc(io, input_png_path, gpa, .unlimited); defer gpa.free(input_png_data); const png_image = try STBImage.load(input_png_data); defer png_image.deinit(); std.debug.assert(png_image.width > 0 and png_image.width <= 256); std.debug.assert(png_image.height > 0 and png_image.height <= 256); const output_ico_file = try cwd.createFile(io, output_ico_path, .{}); defer output_ico_file.close(io); var buffer: [4096 * 4]u8 = undefined; var file_writer = output_ico_file.writer(io, &buffer); const writer = &file_writer.interface; // ICONDIR structure try writer.writeInt(u16, 0, .little); // Must always be zero try writer.writeInt(u16, 1, .little); // Image type. 1 for .ICO try writer.writeInt(u16, 1, .little); // Number of images // ICONDIRENTRY structure try writer.writeInt(u8, @truncate(png_image.width), .little); // Image width try writer.writeInt(u8, @truncate(png_image.height), .little); // Image height try writer.writeInt(u8, 0, .little); // Number of colors in color pallete. 0 means that color pallete is not used try writer.writeInt(u8, 0, .little); // Must always be zero try writer.writeInt(u16, 0, .little); // Color plane try writer.writeInt(u16, 32, .little); // Bits per pixel try writer.writeInt(u32, @intCast(input_png_data.len), .little); // Image size in bytes try writer.writeInt(u32, 22, .little); // Offset to image data from the start // PNG image data try writer.writeAll(input_png_data); try writer.flush(); }