a73x

src/tools/imgdiff.zig

Ref:   Size: 2.7 KiB

const std = @import("std");
const png = @import("png");
const imgdiff = @import("imgdiff");

pub fn main() !void {
    var gpa: std.heap.DebugAllocator(.{}) = .init;
    defer _ = gpa.deinit();
    const alloc = gpa.allocator();

    const args = try std.process.argsAlloc(alloc);
    defer std.process.argsFree(alloc, args);

    if (args.len < 3) {
        std.debug.print("usage: imgdiff <actual.png> <reference.png> [diff.png]\n", .{});
        std.process.exit(2);
    }
    const actual_path = args[1];
    const reference_path = args[2];
    const diff_path: ?[]const u8 = if (args.len >= 4) args[3] else null;

    const rmse_max = readFloatEnv("WAYSTTY_TEST_RMSE_MAX", imgdiff.RMSE_DEFAULT);
    const pixel_max = readFloatEnv("WAYSTTY_TEST_PIXEL_MAX", imgdiff.PIXEL_MAX_DEFAULT);

    const actual_bytes = try std.fs.cwd().readFileAlloc(alloc, actual_path, 64 * 1024 * 1024);
    defer alloc.free(actual_bytes);
    const reference_bytes = try std.fs.cwd().readFileAlloc(alloc, reference_path, 64 * 1024 * 1024);
    defer alloc.free(reference_bytes);

    var actual = try png.decode(alloc, actual_bytes);
    defer actual.deinit(alloc);
    var reference = try png.decode(alloc, reference_bytes);
    defer reference.deinit(alloc);

    if (actual.width != reference.width or actual.height != reference.height) {
        std.debug.print("FAIL: dimensions differ ({}x{} vs {}x{})\n", .{ actual.width, actual.height, reference.width, reference.height });
        std.process.exit(3);
    }

    const r = try imgdiff.compare(actual, reference);
    const pass = r.rmse <= rmse_max and r.max_pixel <= pixel_max;

    if (pass) {
        std.debug.print("OK:   {s}  RMSE={d:.4}%  worst={d:.4}%\n", .{ reference_path, r.rmse * 100.0, r.max_pixel * 100.0 });
        std.process.exit(0);
    }

    std.debug.print("FAIL: {s}\n  RMSE: {d:.4}% (max {d:.4}%)\n  worst pixel: {d:.4}% (max {d:.4}%)\n", .{ reference_path, r.rmse * 100.0, rmse_max * 100.0, r.max_pixel * 100.0, pixel_max * 100.0 });

    if (diff_path) |p| {
        const diff_img = try imgdiff.makeDiffImage(alloc, actual, reference);
        defer alloc.free(diff_img.pixels);

        var buf: std.ArrayList(u8) = .empty;
        defer buf.deinit(alloc);
        try png.encode(alloc, diff_img, buf.writer(alloc));

        const out = try std.fs.cwd().createFile(p, .{ .truncate = true });
        defer out.close();
        try out.writeAll(buf.items);

        std.debug.print("  diff: {s}\n", .{p});
    }
    std.debug.print("  actual: {s}\n", .{actual_path});
    std.process.exit(1);
}

fn readFloatEnv(name: []const u8, default: f64) f64 {
    const val = std.posix.getenv(name) orelse return default;
    return std.fmt.parseFloat(f64, val) catch default;
}