sokoban-v2/scripts/png-to-icon.zig

58 lines
2.1 KiB
Zig

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 <png-file> <output-ico>", .{});
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();
}