Skip to content

Commit 4b23ed3

Browse files
author
Adrià Arrufat
committed
image: add sharpen image and update example
1 parent 9fe606d commit 4b23ed3

File tree

3 files changed

+116
-9
lines changed

3 files changed

+116
-9
lines changed

examples/lib/face-alignment.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
</div>
2929
<div class="blurring-slider">
3030
<label for="blurring-range">blurring:</label>
31-
<input type="range" min="0" max="100" value="0" class="slider" name="blurring-range">
31+
<input type="range" min="-50" max="50" value="0" class="slider" name="blurring-range">
3232
<label for="blurring-range"><code><span name="blurring"></span></code></label>
3333
</div>
3434
</div>

examples/src/face_alignment.zig

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub fn extractAlignedFace(
2525
image: Image(T),
2626
landmarks: []const Point2d,
2727
padding: f32,
28-
blurring: usize,
28+
blurring: i32,
2929
out: *Image(T),
3030
) !void {
3131
// This are the normalized coordinates of the aligned landmarks
@@ -76,11 +76,11 @@ pub fn extractAlignedFace(
7676
c.* = b;
7777
}
7878

79-
var integral: Image([4]f32) = undefined;
80-
try out.integralImage(allocator, &integral);
81-
defer integral.deinit(allocator);
82-
83-
try out.boxBlur(allocator, out, blurring);
79+
if (blurring > 0) {
80+
try out.boxBlur(allocator, out, @intCast(blurring));
81+
} else if (blurring < 0) {
82+
try out.sharpen(allocator, out, @intCast(-blurring));
83+
}
8484
}
8585

8686
pub export fn extract_aligned_face(
@@ -91,7 +91,7 @@ pub export fn extract_aligned_face(
9191
out_rows: usize,
9292
out_cols: usize,
9393
padding: f32,
94-
blurring: usize,
94+
blurring: i32,
9595
landmarks_ptr: [*]const Point2d,
9696
landmarks_len: usize,
9797
extra_ptr: [*]u8,

src/image.zig

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@ pub fn Image(comptime T: type) type {
294294
.Struct => {
295295
const num_fields = std.meta.fields(T).len;
296296
var integral: Image([num_fields]f32) = undefined;
297-
defer integral.deinit(allocator);
298297
try self.integralImage(allocator, &integral);
298+
defer integral.deinit(allocator);
299299
const size = self.rows * self.cols;
300300
var pos: usize = 0;
301301
var rem: usize = size;
@@ -328,6 +328,113 @@ pub fn Image(comptime T: type) type {
328328
else => @compileError("Can't compute the boxBlur image of " ++ @typeName(T) ++ "."),
329329
}
330330
}
331+
332+
/// Computes a sharpened version of self efficiently by using an integral image.
333+
/// The code is almost exactly the same as in blurBox, except that we compute the
334+
/// sharpened version as: sharpened = 2 * self - blurred.
335+
pub fn sharpen(self: Self, allocator: std.mem.Allocator, sharpened: *Self, radius: usize) !void {
336+
if (!self.hasSameShape(sharpened.*)) {
337+
sharpened.* = try Image(T).initAlloc(allocator, self.rows, self.cols);
338+
}
339+
if (radius == 0) {
340+
for (self.data, sharpened.data) |s, *b| b.* = s;
341+
return;
342+
}
343+
344+
switch (@typeInfo(T)) {
345+
.Int, .Float => {
346+
var integral: Image(f32) = undefined;
347+
defer integral.deinit(allocator);
348+
try self.integralImage(allocator, &integral);
349+
const size = self.rows * self.cols;
350+
var pos: usize = 0;
351+
var rem: usize = size;
352+
const simd_len = std.simd.suggestVectorLength(T) orelse 1;
353+
const box_areas: @Vector(simd_len, f32) = @splat(2 * radius * 2 * radius);
354+
while (pos < size) {
355+
const r = pos / self.cols;
356+
const c = pos % self.cols;
357+
const r1 = r -| radius;
358+
const r2 = @min(r + radius, self.rows - 1);
359+
const r1_offset = r1 * self.cols;
360+
const r2_offset = r2 * self.cols;
361+
if (r1 >= radius and r2 <= self.rows - 1 - radius and
362+
c >= radius and c <= self.cols - 1 - radius - simd_len and
363+
rem >= simd_len)
364+
{
365+
const c1 = c - radius;
366+
const c2 = c + radius;
367+
const int11s: @Vector(simd_len, f32) = integral.data[r1_offset + c1 ..][0..simd_len];
368+
const int12s: @Vector(simd_len, f32) = integral.data[r1_offset + c2 ..][0..simd_len];
369+
const int21s: @Vector(simd_len, f32) = integral.data[r2_offset + c1 ..][0..simd_len];
370+
const int22s: @Vector(simd_len, f32) = integral.data[r2_offset + c2 ..][0..simd_len];
371+
const sums = int22s - int21s - int12s + int11s;
372+
const vals: [simd_len]f32 = @round(sums / box_areas);
373+
for (vals, 0..) |val, i| {
374+
if (@typeInfo(T) == .ComptimeInt or @typeInfo(T) == .Int) {
375+
const temp = @max(0, @min(std.math.maxInt(T), as(isize, self.data[pos]) * 2 - @as(isize, val)));
376+
sharpened.data[pos + i] = as(T, temp);
377+
} else {
378+
sharpened.data[pos + i] = 2 * self.data[pos] - val;
379+
}
380+
}
381+
pos += simd_len;
382+
rem -= simd_len;
383+
} else {
384+
const c1 = c -| radius;
385+
const c2 = @min(c + radius, self.cols - 1);
386+
const pos11 = r1_offset + c1;
387+
const pos12 = r1_offset + c2;
388+
const pos21 = r2_offset + c1;
389+
const pos22 = r2_offset + c2;
390+
const area: f32 = @floatFromInt((r2 - r1) * (c2 - c1));
391+
const sum = integral.data[pos22] - integral.data[pos21] - integral.data[pos12] + integral.data[pos11];
392+
sharpened.data[pos] = switch (@typeInfo(T)) {
393+
.Int, .ComptimeInt => as(T, @round(sum / area)),
394+
.Float, .ComptimeFloat => as(T, sum / area),
395+
};
396+
pos += 1;
397+
rem -= 1;
398+
}
399+
}
400+
},
401+
.Struct => {
402+
const num_fields = std.meta.fields(T).len;
403+
var integral: Image([num_fields]f32) = undefined;
404+
try self.integralImage(allocator, &integral);
405+
defer integral.deinit(allocator);
406+
const size = self.rows * self.cols;
407+
var pos: usize = 0;
408+
var rem: usize = size;
409+
while (pos < size) {
410+
const r = pos / self.cols;
411+
const c = pos % self.cols;
412+
const r1 = r -| radius;
413+
const c1 = c -| radius;
414+
const r2 = @min(r + radius, self.rows - 1);
415+
const c2 = @min(c + radius, self.cols - 1);
416+
const r1_offset = r1 * self.cols;
417+
const r2_offset = r2 * self.cols;
418+
const pos11 = r1_offset + c1;
419+
const pos12 = r1_offset + c2;
420+
const pos21 = r2_offset + c1;
421+
const pos22 = r2_offset + c2;
422+
const area: f32 = @floatFromInt((r2 - r1) * (c2 - c1));
423+
inline for (std.meta.fields(T), 0..) |f, i| {
424+
const sum = integral.data[pos22][i] - integral.data[pos21][i] - integral.data[pos12][i] + integral.data[pos11][i];
425+
@field(sharpened.data[pos], f.name) = switch (@typeInfo(f.type)) {
426+
.Int => as(f.type, @max(0, @min(std.math.maxInt(f.type), as(isize, @field(self.data[pos], f.name)) * 2 - as(isize, @round(sum / area))))),
427+
.Float => as(f.type, 2 * @field(self.data[pos], f.name) - sum / area),
428+
else => @compileError("Can't compute the sharpen image with struct fields of type " ++ @typeName(f.type) ++ "."),
429+
};
430+
}
431+
pos += 1;
432+
rem -= 1;
433+
}
434+
},
435+
else => @compileError("Can't compute the sharpen image of " ++ @typeName(T) ++ "."),
436+
}
437+
}
331438
};
332439
}
333440

0 commit comments

Comments
 (0)