Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion odiff/.gitignore → odiff-lib/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@

.zig-cache/
node_modules/
zig-out/
95 changes: 95 additions & 0 deletions odiff-lib/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
const std = @import("std");
const manifest = @import("build.zig.zon");

const ndk = @import("ndk.zig");
const getAndroidTriple = ndk.getAndroidTriple;
const getOutputDir = ndk.getOutputDir;
const createLibC = ndk.createLibC;
const ndk_path = ndk.DEFAULT_NDK_PATH;
const ndk_version = ndk.DEFAULT_NDK_VERSION;
const android_api_version = ndk.DEFAULT_ANDROID_API_VERSION;

const build_targets: []const std.Target.Query = &.{
.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .android, .android_api_level = 24 },
.{ .cpu_arch = .arm, .os_tag = .linux, .abi = .androideabi, .android_api_level = 24 },
.{ .cpu_arch = .x86, .os_tag = .linux, .abi = .android, .android_api_level = 24 },
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .android, .android_api_level = 24 },
};

pub fn build(b: *std.Build) !void {
const optimize = b.standardOptimizeOption(.{});

const build_options = b.addOptions();
build_options.addOption([]const u8, "version", manifest.version);

for (build_targets) |target_query| {
const target = b.resolveTargetQuery(target_query);
const odiff = b.dependency("odiff", .{
.target = target,
.optimize = optimize,
});

const odiff_lib = odiff.artifact("odiff-lib");

const mod = b.addModule("odiff-android-lib", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "odiff-lib", .module = odiff_lib.root_module },
},
});

const root_lib = b.addLibrary(.{
.name = "odiff-lib",
.root_module = mod,
.linkage = .static,
});

const abi_output_dir = getOutputDir(target.result) catch |err| {
std.log.err("Unsupported target architecture: {}", .{target.result.cpu.arch});
return err;
};

const header_output_dir = try std.fs.path.join(b.allocator, &.{ abi_output_dir, "include" });

const android_triple = try getAndroidTriple(target.result);
const libc_config = createLibC(b, android_triple, android_api_version, ndk_path, ndk_version);

odiff_lib.setLibCFile(libc_config);
odiff_lib.linkLibC();
b.installArtifact(odiff_lib);

const c_test_mod = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
});
c_test_mod.addCSourceFiles(.{
.files = &.{"src/test-odiff.c"},
.flags = &.{},
});

const c_test_exe = b.addExecutable(.{
.name = "test_odiff",
.root_module = c_test_mod,
});
c_test_exe.linkLibrary(root_lib);

const install_artifact = b.addInstallArtifact(odiff_lib, .{
.h_dir = .{
.override = .{
.custom = header_output_dir,
},
},
.dest_dir = .{
.override = .{
.custom = abi_output_dir,
},
},
});

b.getInstallStep().dependOn(&install_artifact.step);
}
}
18 changes: 18 additions & 0 deletions odiff-lib/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.{
.name = .odiff_android_lib,
.version = "0.0.0",
.fingerprint = 0x7b4b77cc05107eb1,
.minimum_zig_version = "0.15.1",
.dependencies = .{
.url = "git+https://github.com/shreyassanthu77/odiff?ref=refactor-tests#1f53db8c9b882ba84dd9750e0908df4a99476a5d",
.hash = "odiff-4.1.1-bDbIYd0cAgB8D9q44E3OtsAnzXSWNkp1SxTYdF2vask9",
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}
File renamed without changes.
136 changes: 136 additions & 0 deletions odiff-lib/src/c_odiff_api.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const std = @import("std");
const odiff = @import("odiff-lib").diff;
const image_io = @import("odiff-lib").io;

// C-compatible structs
pub const CDiffOptions = extern struct {
antialiasing: bool,
output_diff_mask: bool,
diff_overlay_factor: f32, // 0.0 if not set
diff_lines: bool,
diff_pixel: u32,
threshold: f64,
fail_on_layout_change: bool,
enable_asm: bool,
ignore_region_count: usize,
ignore_regions: ?[*]const odiff.IgnoreRegion,
};

pub const CDiffResult = extern struct {
result_type: c_int, // 0 = layout, 1 = pixel
diff_count: u32,
diff_percentage: f64,
diff_line_count: usize,
diff_lines: ?[*]u32,
diff_output_path: ?[*:0]const u8, // if saved
};

pub const COdiffError = enum(c_int) {
success = 0,
image_not_loaded = 1,
unsupported_format = 2,
failed_to_diff = 3,
out_of_memory = 4,
invalid_hex_color = 5,
};

fn parseHexColor(hex: []const u8) !u32 {
if (hex.len != 7 or hex[0] != '#') return error.InvalidHexColor;
return std.fmt.parseInt(u32, hex[1..], 16) catch error.InvalidHexColor;
}

pub export fn odiff_diff(
base_image_path: [*:0]const u8,
comp_image_path: [*:0]const u8,
diff_output_path: ?[*:0]const u8,
c_options: CDiffOptions,
) COdiffError {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();

var base_img = image_io.loadImage(allocator, std.mem.span(base_image_path)) catch |err| switch (err) {
error.ImageNotLoaded => return .image_not_loaded,
error.UnsupportedFormat => return .unsupported_format,
else => return .failed_to_diff,
};
defer base_img.deinit(allocator);

var comp_img = image_io.loadImage(allocator, std.mem.span(comp_image_path)) catch |err| switch (err) {
error.ImageNotLoaded => return .image_not_loaded,
error.UnsupportedFormat => return .unsupported_format,
else => return .failed_to_diff,
};
defer comp_img.deinit(allocator);

var ignore_regions: ?[]const odiff.IgnoreRegion = null;
if (c_options.ignore_regions) |regions| {
ignore_regions = regions[0..c_options.ignore_region_count];
}

const diff_options = odiff.DiffOptions{
.antialiasing = c_options.antialiasing,
.output_diff_mask = c_options.output_diff_mask,
.diff_overlay_factor = if (c_options.diff_overlay_factor > 0.0) c_options.diff_overlay_factor else null,
.diff_lines = c_options.diff_lines,
.diff_pixel = c_options.diff_pixel,
.threshold = c_options.threshold,
.ignore_regions = ignore_regions,
.capture_diff = diff_output_path != null,
.fail_on_layout_change = c_options.fail_on_layout_change,
.enable_asm = c_options.enable_asm,
};

const result = odiff.diff(&base_img, &comp_img, diff_options, allocator) catch return .failed_to_diff;

switch (result) {
.layout => {
return .success; // Or define a way to indicate layout diff
},
.pixel => |pixel_result| {
defer {
if (pixel_result.diff_output) |*output| {
var img = output.*;
img.deinit(allocator);
}
if (pixel_result.diff_lines) |lines| {
var mutable_lines = lines;
mutable_lines.deinit();
}
}

if (diff_output_path) |output_path| {
if (pixel_result.diff_output) |output_img| {
image_io.saveImage(output_img, std.mem.span(output_path)) catch return .failed_to_diff;
}
}

return .success;
},
}
}

pub export fn odiff_free_diff_lines(diff_lines: [*]u32, count: usize) void {
const slice = diff_lines[0..count];
std.heap.page_allocator.free(slice);
}

pub export fn parse_hex_color(hex: [*:0]const u8) u32 {
const hex_str = std.mem.span(hex);
if (hex_str.len == 0) return 0xFF0000FF;

var color_str = hex_str;
if (hex_str[0] == '#') {
color_str = hex_str[1..];
}

if (color_str.len != 6) {
@panic("Invalid Hex Color");
}

const r = std.fmt.parseInt(u8, color_str[0..2], 16) catch @panic("R is missing");
const g = std.fmt.parseInt(u8, color_str[2..4], 16) catch @panic("G is missing");
const b = std.fmt.parseInt(u8, color_str[4..6], 16) catch @panic("B is missing");

return (@as(u32, 255) << 24) | (@as(u32, b) << 16) | (@as(u32, g) << 8) | @as(u32, r);
}
10 changes: 1 addition & 9 deletions odiff/src/root.zig → odiff-lib/src/root.zig
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
const std = @import("std");

pub const cli = @import("cli.zig");
pub const image_io = @import("image_io.zig");
pub const diff = @import("diff.zig");
pub const color_delta = @import("color_delta.zig");
pub const antialiasing = @import("antialiasing.zig");
pub const bmp_reader = @import("bmp_reader.zig");
pub const c_bindings = @import("c_bindings.zig");
pub const c_api = @import("c_api.zig");
pub const c_api = @import("c_odiff_api.zig");

// Ensure C API functions are included
comptime {
_ = c_api.odiff_diff;
_ = c_api.odiff_diff_with_results;
_ = c_api.odiff_free_diff_lines;
}

Expand Down
75 changes: 75 additions & 0 deletions odiff-lib/src/test-odiff.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
int antialiasing;
int output_diff_mask;
float diff_overlay_factor;
int diff_lines;
unsigned int diff_pixel;
double threshold;
int fail_on_layout_change;
int enable_asm;
size_t ignore_region_count;
void* ignore_regions; // array of IgnoreRegion
} CDiffOptions;

typedef struct {
int result_type; // 0 = layout, 1 = pixel
unsigned int diff_count;
double diff_percentage;
size_t diff_line_count;
unsigned int* diff_lines;
const char* diff_output_path;
} CDiffResult;

typedef enum {
SUCCESS = 0,
IMAGE_NOT_LOADED = 1,
UNSUPPORTED_FORMAT = 2,
FAILED_TO_DIFF = 3,
OUT_OF_MEMORY = 4,
INVALID_HEX_COLOR = 5,
} COdiffError;

// Declare the exported functions
COdiffError odiff_diff(const char* base_image_path, const char* comp_image_path, const char* diff_output_path, CDiffOptions options);
COdiffError odiff_diff_with_results(const char* base_image_path, const char* comp_image_path, const char* diff_output_path, CDiffOptions options, CDiffResult* out_result);
void odiff_free_diff_lines(unsigned int* diff_lines, size_t count);
int parse_hex_color(const char* hex_str);

// No custom allocator needed

int main(int argc, char* argv[]) {
if (argc < 3) {
printf("Usage: %s <base_image> <comp_image> [diff_output]\n", argv[0]);
return 1;
}

const char* base_path = argv[1];
const char* comp_path = argv[2];
const char* diff_output = (argc >= 4) ? argv[3] : NULL;

int diff_pixel = parse_hex_color("");

// Set up options
CDiffOptions options = {
.antialiasing = 0,
.output_diff_mask = 0,
.diff_overlay_factor = 0.0f,
.diff_lines = 0,
.diff_pixel = diff_pixel,
.threshold = 0.1,
.fail_on_layout_change = 0,
.enable_asm = 0,
.ignore_region_count = 0,
.ignore_regions = NULL,
};

CDiffResult result;
COdiffError err = odiff_diff(base_path, comp_path, diff_output, options);

return 0;
}

22 changes: 0 additions & 22 deletions odiff/LICENSE.txt

This file was deleted.

Loading