add .ico file generation at build-time

This commit is contained in:
Rokas Puzonas 2025-02-08 00:25:43 +02:00
parent 6eea7329c8
commit f563ff931b
12 changed files with 8190 additions and 11 deletions

106
build.zig
View File

@ -1,6 +1,83 @@
const std = @import("std"); const std = @import("std");
const Module = std.Build.Module; const Module = std.Build.Module;
const AddExecutableIcon = struct {
obj: *std.Build.Step.Compile,
step: std.Build.Step,
icon_file: std.Build.LazyPath,
resource_file: std.Build.LazyPath,
fn init(obj: *std.Build.Step.Compile, icon_file: std.Build.LazyPath) *AddExecutableIcon {
const b = obj.step.owner;
const self = b.allocator.create(AddExecutableIcon) catch @panic("OOM");
self.obj = obj;
self.step = std.Build.Step.init(.{
.id = .custom,
.name = "add executable icon",
.owner = b,
.makeFn = make
});
self.icon_file = icon_file;
icon_file.addStepDependencies(&self.step);
const write_files = b.addWriteFiles();
self.resource_file = write_files.add("resource-file.rc", "");
self.step.dependOn(&write_files.step);
self.obj.addWin32ResourceFile(.{
.file = self.resource_file,
});
return self;
}
fn make(step: *std.Build.Step, _: std.Progress.Node) !void {
const b = step.owner;
const self: *AddExecutableIcon = @fieldParentPtr("step", step);
const resource_file = try std.fs.createFileAbsolute(self.resource_file.getPath(b), .{ });
defer resource_file.close();
const relative_icon_path = try std.fs.path.relative(
b.allocator,
self.resource_file.dirname().getPath(b),
self.icon_file.getPath(b)
);
std.mem.replaceScalar(u8, relative_icon_path, '\\', '/');
try resource_file.writeAll("IDI_ICON ICON \"");
try resource_file.writeAll(relative_icon_path);
try resource_file.writeAll("\"");
}
};
fn buildStbImage(b: *std.Build) *std.Build.Module {
const module = b.createModule(.{
.root_source_file = b.path("libs/stb_image/root.zig"),
.link_libc = true
});
module.addCSourceFile(.{ .file = b.path("libs/stb_image/stb_image.c") });
module.addIncludePath(b.path("libs/stb_image/"));
return module;
}
fn buildCuteAseprite(b: *std.Build, raylib_dep: *std.Build.Dependency) *std.Build.Module {
const module = b.createModule(.{
.root_source_file = b.path("libs/cute_aseprite/root.zig"),
});
module.addCSourceFile(.{ .file = b.path("libs/cute_aseprite/cute_aseprite.c") });
module.addIncludePath(b.path("libs/cute_aseprite/"));
module.linkLibrary(raylib_dep.artifact("raylib"));
module.addImport("raylib", raylib_dep.module("raylib"));
return module;
}
pub fn build(b: *std.Build) !void { pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
@ -10,6 +87,18 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize, .optimize = optimize,
}); });
const stb_image_lib = buildStbImage(b);
const cute_aseprite_lib = buildCuteAseprite(b, raylib_dep);
const png_to_icon_tool = b.addExecutable(.{
.name = "png-to-icon",
.root_source_file = b.path("tools/png-to-icon.zig"),
.target = target,
});
png_to_icon_tool.root_module.addImport("stb_image", stb_image_lib);
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "daq-view", .name = "daq-view",
.root_source_file = b.path("src/main.zig"), .root_source_file = b.path("src/main.zig"),
@ -17,12 +106,10 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize, .optimize = optimize,
}); });
exe.addIncludePath(b.path("src"));
exe.addCSourceFile(.{
.file = b.path("src/cute_aseprite.c")
});
exe.linkLibrary(raylib_dep.artifact("raylib")); exe.linkLibrary(raylib_dep.artifact("raylib"));
exe.root_module.addImport("raylib", raylib_dep.module("raylib")); exe.root_module.addImport("raylib", raylib_dep.module("raylib"));
exe.root_module.addImport("stb_image", stb_image_lib);
exe.root_module.addImport("cute_aseprite", cute_aseprite_lib);
const external_compiler_support_dir = try std.process.getEnvVarOwned(b.allocator, "NIEXTCCOMPILERSUPP"); const external_compiler_support_dir = try std.process.getEnvVarOwned(b.allocator, "NIEXTCCOMPILERSUPP");
exe.addSystemIncludePath(.{ .cwd_relative = try std.fs.path.join(b.allocator, &.{ external_compiler_support_dir, "include" }) }); exe.addSystemIncludePath(.{ .cwd_relative = try std.fs.path.join(b.allocator, &.{ external_compiler_support_dir, "include" }) });
@ -33,13 +120,12 @@ pub fn build(b: *std.Build) !void {
if (target.result.os.tag == .windows) { if (target.result.os.tag == .windows) {
exe.subsystem = if (optimize == .Debug) .Console else .Windows; exe.subsystem = if (optimize == .Debug) .Console else .Windows;
const resource_file = b.addWriteFiles(); const png_to_icon_step = b.addRunArtifact(png_to_icon_tool);
png_to_icon_step.addFileArg(b.path("src/assets/icon.png"));
const icon_file = png_to_icon_step.addOutputFileArg("icon.ico");
// https://www.ryanliptak.com/blog/zig-is-a-windows-resource-compiler/ const add_icon_step = AddExecutableIcon.init(exe, icon_file);
// TODO: Generate icon file at build time exe.step.dependOn(&add_icon_step.step);
exe.addWin32ResourceFile(.{
.file = resource_file.add("daq-view.rc", "IDI_ICON ICON \"./src/assets/icon.ico\""),
});
exe.linkSystemLibrary("Comdlg32"); exe.linkSystemLibrary("Comdlg32");
} }

28
libs/stb_image/root.zig Normal file
View File

@ -0,0 +1,28 @@
const std = @import("std");
const assert = std.debug.assert;
extern fn zig_stbi_load_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, comp: ?*i32, req_comp: i32) callconv(.C) ?[*]u8;
extern fn zig_stbi_image_free(buffer: ?*anyopaque) callconv(.C) void;
pub const Image = struct {
width: u32,
height: u32,
rgba: []u8,
pub fn deinit(self: Image) void {
zig_stbi_image_free(self.rgba.ptr);
}
};
pub fn load(buffer: []const u8) !Image {
var width: i32 = 0;
var height: i32 = 0;
const image_rgba = zig_stbi_load_from_memory(buffer.ptr, @intCast(buffer.len), &width, &height, null, 4);
if (image_rgba == null) {
return error.PNGDecode;
}
errdefer zig_stbi_image_free(image_rgba);
const byte_count: u32 = @intCast(width * height * 4);
return Image{ .width = @intCast(width), .height = @intCast(height), .rgba = image_rgba.?[0..byte_count] };
}

View File

@ -0,0 +1,23 @@
#include <stdint.h>
// TODO: Use zig allocators
extern void *stb_image_zig_malloc(uint32_t amount);
extern void *stb_image_zig_realloc(void *mem, uint32_t amount);
extern void stb_image_zig_free(void *mem);
#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_STDIO
#define STBI_ONLY_PNG
#define STB_IMAGE_STATIC
#include "stb_image.h"
void zig_stbi_image_free(void *retval_from_stbi_load)
{
return stbi_image_free(retval_from_stbi_load);
}
stbi_uc *zig_stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
{
return stbi_load_from_memory(buffer, len, x, y, comp, req_comp);
}

7988
libs/stb_image/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@ const std = @import("std");
const rl = @import("raylib"); const rl = @import("raylib");
const srcery = @import("./srcery.zig"); const srcery = @import("./srcery.zig");
const FontFace = @import("./font-face.zig"); const FontFace = @import("./font-face.zig");
const Aseprite = @import("./aseprite.zig"); const Aseprite = @import("cute_aseprite");
const assert = std.debug.assert; const assert = std.debug.assert;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

54
tools/png-to-icon.zig Normal file
View File

@ -0,0 +1,54 @@
const std = @import("std");
const stb_image = @import("stb_image");
// https://en.wikipedia.org/wiki/ICO_(file_format)#Icon_file_structure
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
if (args.len != 3) {
std.debug.print("Usage: ./png-to-icon <png-file> <output-ico>", .{});
std.process.exit(1);
}
const cwd = std.fs.cwd();
const input_png_path = args[1];
const output_ico_path = args[2];
const input_png_data = try cwd.readFileAlloc(allocator, input_png_path, 1024 * 1024 * 5);
defer allocator.free(input_png_data);
const png_image = try stb_image.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 std.fs.cwd().createFile(output_ico_path, .{ });
defer output_ico_file.close();
const writer = output_ico_file.writer();
// 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);
}