Skip to content

Commit 25b8ce6

Browse files
author
Adrià Arrufat
committed
draw: allow drawing in any colorspace, regardless of the image type
1 parent eea785d commit 25b8ce6

File tree

3 files changed

+25
-6
lines changed

3 files changed

+25
-6
lines changed

examples/src/face_alignment.zig

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const Image = zignal.Image;
77
const SimilarityTransform = zignal.SimilarityTransform(f32);
88
const Rectangle = zignal.Rectangle(f32);
99
const Rgba = zignal.Rgba;
10+
const Hsv = zignal.Hsv;
1011
const drawRectangle = zignal.drawRectangle;
1112

1213
pub const std_options: std.Options = .{
@@ -36,7 +37,7 @@ pub fn extractAlignedFace(
3637
blurring: i32,
3738
out: *Image(T),
3839
) !void {
39-
// This are the normalized coordinates of the aligned landmarks
40+
// These are the normalized coordinates of the aligned landmarks
4041
// taken from dlib.
4142
var from_points: [5]Point2d = .{
4243
.{ .x = 0.8595674595992, .y = 0.2134981538014 },
@@ -46,6 +47,7 @@ pub fn extractAlignedFace(
4647
.{ .x = 0.4901123135679, .y = 0.6277975316475 },
4748
};
4849

50+
// These are the detected points from MediaPipe.
4951
const to_points: [5]Point2d = .{
5052
landmarks[alignment[0]].scale(image.cols, image.rows),
5153
landmarks[alignment[1]].scale(image.cols, image.rows),
@@ -61,30 +63,43 @@ pub fn extractAlignedFace(
6163
p.x = (padding + p.x) / (2 * padding + 1) * side;
6264
p.y = (padding + p.y) / (2 * padding + 1) * side;
6365
}
66+
67+
// Find the transforms that maps the points between the canonical landmarks
68+
// and the detected landmarks.
6469
const transform = SimilarityTransform.find(&from_points, &to_points);
6570
var p = transform.project(.{ .x = 1, .y = 0 });
6671
p.x -= transform.bias.at(0, 0);
6772
p.y -= transform.bias.at(1, 0);
6873
const angle = std.math.atan2(p.y, p.x);
6974
const scale = p.norm();
7075
const center = transform.project(.{ .x = side / 2, .y = side / 2 });
76+
77+
// Align the face: rotate the image so that the face is aligned.
7178
var rotated: Image(Rgba) = undefined;
7279
try image.rotateFrom(allocator, center, angle, &rotated);
7380
defer rotated.deinit(allocator);
7481

82+
// Get the rectangle around the face.
7583
const rect = Rectangle.initCenter(center.x, center.y, side * scale, side * scale);
76-
drawRectangle(Rgba, image, rect, 1, .{ .r = 0, .g = 0, .b = 0, .a = 255 });
84+
// Draw a rectangle around the detected face. Note that the color can be any Zignal
85+
// supported color, and the appropriate conversion will be performed. In this case,
86+
// the image is in Rgba format, and the color is in Hsv. Zignal will handle that.
87+
drawRectangle(Rgba, image, rect, 1, Hsv{ .h = 0, .s = 100, .v = 100 });
88+
89+
// Crop out the detected face.
7790
var chip: Image(Rgba) = undefined;
7891
try rotated.crop(allocator, rect, &chip);
7992
defer chip.deinit(allocator);
8093

94+
// Resize it to the desired size.
8195
var resized = try Image(Rgba).initAlloc(allocator, out.rows, out.cols);
8296
defer resized.deinit(allocator);
8397
chip.resize(&resized);
8498
for (out.data, resized.data) |*c, b| {
8599
c.* = b;
86100
}
87101

102+
// Perform blurring or sharpening to the aligned face.
88103
if (blurring > 0) {
89104
try out.boxBlur(allocator, out, @intCast(blurring));
90105
} else if (blurring < 0) {

src/color.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub const Color = union(enum) {
1515
Lab: Lab,
1616

1717
/// Returns true if and only if T can be treated as a color.
18-
fn isColor(comptime T: type) bool {
18+
pub fn isColor(comptime T: type) bool {
1919
return switch (T) {
2020
u8, Rgb, Rgba, Hsv, Lab => true,
2121
else => false,

src/draw.zig

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const std = @import("std");
2+
const assert = std.debug.assert;
23
const as = @import("meta.zig").as;
34
const Color = @import("color.zig").Color;
45
const Rgba = @import("color.zig").Rgba;
@@ -9,7 +10,8 @@ const Rectangle = @import("geometry.zig").Rectangle(f32);
910
/// Draws a colored straight of a custom width between p1 and p2 on image. It uses Xialin
1011
/// Wu's line algorithm to perform antialiasing along the diagonal. Moreover, if color is of
1112
/// Rgba type, it alpha-blends it on top of the image.
12-
pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, width: usize, color: T) void {
13+
pub fn drawLine(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d, width: usize, color: anytype) void {
14+
comptime assert(Color.isColor(@TypeOf(color)));
1315
if (width == 0) return;
1416
// To avoid casting all the time, perform all operations using the underlying type of p1 and p2.
1517
const Float = @TypeOf(p1.x);
@@ -197,7 +199,8 @@ pub fn drawLineFast(comptime T: type, image: Image(T), p1: Point2d, p2: Point2d,
197199
}
198200
}
199201

200-
pub fn drawRectangle(comptime T: type, image: Image(T), rect: Rectangle, width: usize, color: T) void {
202+
pub fn drawRectangle(comptime T: type, image: Image(T), rect: Rectangle, width: usize, color: anytype) void {
203+
comptime assert(Color.isColor(@TypeOf(color)));
201204
const points: []const Point2d = &.{
202205
.{ .x = rect.l, .y = rect.t },
203206
.{ .x = rect.r, .y = rect.t },
@@ -221,7 +224,8 @@ pub fn drawCross(comptime T: type, image: Image(T), center: Point2d, size: usize
221224
}
222225

223226
/// Draws the given polygon defined as an array of points.
224-
pub fn drawPolygon(comptime T: type, image: Image(T), polygon: []const Point2d, width: usize, color: T) void {
227+
pub fn drawPolygon(comptime T: type, image: Image(T), polygon: []const Point2d, width: usize, color: anytype) void {
228+
comptime assert(Color.isColor(@TypeOf(color)));
225229
if (width == 0) return;
226230
for (0..polygon.len) |i| {
227231
drawLine(T, image, polygon[i], polygon[@mod(i + 1, polygon.len)], width, color);

0 commit comments

Comments
 (0)