@@ -294,8 +294,8 @@ pub fn Image(comptime T: type) type {
294
294
.Struct = > {
295
295
const num_fields = std .meta .fields (T ).len ;
296
296
var integral : Image ([num_fields ]f32 ) = undefined ;
297
- defer integral .deinit (allocator );
298
297
try self .integralImage (allocator , & integral );
298
+ defer integral .deinit (allocator );
299
299
const size = self .rows * self .cols ;
300
300
var pos : usize = 0 ;
301
301
var rem : usize = size ;
@@ -328,6 +328,113 @@ pub fn Image(comptime T: type) type {
328
328
else = > @compileError ("Can't compute the boxBlur image of " ++ @typeName (T ) ++ "." ),
329
329
}
330
330
}
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
+ }
331
438
};
332
439
}
333
440
0 commit comments