lib/simd/src/image/root.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const simd = @import("../root.zig");
  3 const tag = simd.tag;
  4 const highway = simd.aligned;
  5 
  6 pub const vector_size: usize = tag.ScalableTag(u8).byte_count;
  7 pub const storage_alignment: usize = @max(highway.alignment, vector_size);
  8 
  9 pub const GeometryError = error{
 10     AliasedPlanes,
 11     AllocationSizeOverflow,
 12     BufferTooSmall,
 13     DimensionTooLarge,
 14     InvalidComponentSize,
 15     InsufficientRowPadding,
 16     InvalidShrink,
 17     InvalidStride,
 18     InvalidVectorSize,
 19     MisalignedStorage,
 20     PlaneSizeMismatch,
 21     PlaneStrideMismatch,
 22     RowSizeOverflow,
 23 };
 24 pub const Error = std.mem.Allocator.Error || GeometryError;
 25 
 26 const Padding = enum {
 27     round_up,
 28     unaligned,
 29 };
 30 
 31 const Packing = enum {
 32     none,
 33     owned,
 34 };
 35 
 36 pub fn vectorSize() usize {
 37     return vector_size;
 38 }
 39 
 40 pub fn bytesPerRow(xsize: usize, component_size: usize) GeometryError!usize {
 41     return bytesPerRowFor(xsize, component_size, vector_size);
 42 }
 43 
 44 pub fn bytesPerRowFor(
 45     xsize: usize,
 46     component_size: usize,
 47     selected_vector_size: usize,
 48 ) GeometryError!usize {
 49     try validateGeometry(component_size, selected_vector_size);
 50     var valid_bytes = std.math.mul(usize, xsize, component_size) catch
 51         return error.RowSizeOverflow;
 52     if (selected_vector_size != 1) {
 53         valid_bytes = std.math.add(
 54             usize,
 55             valid_bytes,
 56             selected_vector_size - component_size,
 57         ) catch return error.RowSizeOverflow;
 58     }
 59     const alignment = @max(highway.alignment, selected_vector_size);
 60     var stride = try roundUp(valid_bytes, alignment);
 61     if (stride % highway.alignment == 0) {
 62         stride = std.math.add(usize, stride, alignment) catch
 63             return error.RowSizeOverflow;
 64     }
 65     std.debug.assert(stride % alignment == 0);
 66     return stride;
 67 }
 68 
 69 pub fn Image(comptime T: type) type {
 70     validateComponent(T);
 71     return struct {
 72         xsize_value: u32,
 73         ysize_value: u32,
 74         bytes_per_row_value: usize,
 75         storage: []u8,
 76         owned: bool,
 77 
 78         const Self = @This();
 79 
 80         pub fn empty() Self {
 81             return zeroDimensions(0, 0);
 82         }
 83 
 84         fn zeroDimensions(width: usize, height: usize) Self {
 85             return .{
 86                 .xsize_value = @intCast(width),
 87                 .ysize_value = @intCast(height),
 88                 .bytes_per_row_value = 0,
 89                 .storage = &.{},
 90                 .owned = false,
 91             };
 92         }
 93 
 94         pub fn init(allocator: std.mem.Allocator, width: usize, height: usize) Error!Self {
 95             try validateDimensions(width, height);
 96             if (width == 0 or height == 0) return zeroDimensions(width, height);
 97             const stride = try bytesPerRowFor(width, @sizeOf(T), vector_size);
 98             const allocation_size = std.math.mul(usize, stride, height) catch
 99                 return error.AllocationSizeOverflow;
100             const storage = try allocator.alignedAlloc(
101                 u8,
102                 .fromByteUnits(storage_alignment),
103                 allocation_size,
104             );
105             var result = Self{
106                 .xsize_value = @intCast(width),
107                 .ysize_value = @intCast(height),
108                 .bytes_per_row_value = stride,
109                 .storage = storage,
110                 .owned = true,
111             };
112             result.initializePadding(.round_up) catch |err| {
113                 allocator.free(storage);
114                 return err;
115             };
116             return result;
117         }
118 
119         pub fn initBorrowed(
120             width: usize,
121             height: usize,
122             stride: usize,
123             storage: []u8,
124         ) GeometryError!Self {
125             try validateDimensions(width, height);
126             if (width == 0 or height == 0) {
127                 if (stride != 0) return error.InvalidStride;
128                 return zeroDimensions(width, height);
129             }
130             if (stride % vector_size != 0 or stride % @alignOf(T) != 0) {
131                 return error.InvalidStride;
132             }
133             const valid_bytes = std.math.mul(usize, width, @sizeOf(T)) catch
134                 return error.RowSizeOverflow;
135             if (stride < valid_bytes) return error.InvalidStride;
136             const required = std.math.mul(usize, stride, height) catch
137                 return error.AllocationSizeOverflow;
138             if (storage.len < required) return error.BufferTooSmall;
139             if (@intFromPtr(storage.ptr) % @max(vector_size, @alignOf(T)) != 0) {
140                 return error.MisalignedStorage;
141             }
142             return .{
143                 .xsize_value = @intCast(width),
144                 .ysize_value = @intCast(height),
145                 .bytes_per_row_value = stride,
146                 .storage = storage[0..required],
147                 .owned = false,
148             };
149         }
150 
151         pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
152             if (self.owned) {
153                 std.debug.assert(self.storage.len != 0);
154                 const aligned: []align(storage_alignment) u8 = @alignCast(self.storage);
155                 allocator.free(aligned);
156             }
157             self.* = empty();
158         }
159 
160         pub fn swap(self: *Self, other: *Self) void {
161             std.mem.swap(Self, self, other);
162         }
163 
164         pub fn shrinkTo(self: *Self, width: usize, height: usize) GeometryError!void {
165             if (width > self.xsize_value or height > self.ysize_value) {
166                 return error.InvalidShrink;
167             }
168             self.xsize_value = @intCast(width);
169             self.ysize_value = @intCast(height);
170         }
171 
172         pub fn initializePaddingForUnalignedAccesses(self: *Self) GeometryError!void {
173             try self.initializePadding(.unaligned);
174         }
175 
176         pub fn xsize(self: *const Self) usize {
177             return self.xsize_value;
178         }
179 
180         pub fn ysize(self: *const Self) usize {
181             return self.ysize_value;
182         }
183 
184         pub fn bytesPerRow(self: *const Self) usize {
185             return self.bytes_per_row_value;
186         }
187 
188         pub fn pixelsPerRow(self: *const Self) usize {
189             return self.bytes_per_row_value / @sizeOf(T);
190         }
191 
192         pub fn bytes(self: *Self) []u8 {
193             return self.storage;
194         }
195 
196         pub fn constBytes(self: *const Self) []const u8 {
197             return self.storage;
198         }
199 
200         pub fn constRow(self: *const Self, y: usize) []const T {
201             std.debug.assert(y < self.ysize_value);
202             if (self.bytes_per_row_value == 0) return &.{};
203             const begin = y * self.bytes_per_row_value;
204             const row = self.storage[begin..][0..self.bytes_per_row_value];
205             const ptr: [*]const T = @ptrCast(@alignCast(row.ptr));
206             return ptr[0..self.pixelsPerRow()];
207         }
208 
209         pub fn mutableRow(self: *const Self, y: usize) []T {
210             std.debug.assert(y < self.ysize_value);
211             if (self.bytes_per_row_value == 0) return &.{};
212             const begin = y * self.bytes_per_row_value;
213             const row = self.storage[begin..][0..self.bytes_per_row_value];
214             const ptr: [*]T = @ptrCast(@alignCast(row.ptr));
215             return ptr[0..self.pixelsPerRow()];
216         }
217 
218         fn initializePadding(self: *Self, mode: Padding) GeometryError!void {
219             if (self.xsize_value == 0 or self.ysize_value == 0 or vector_size == 1) return;
220             const valid_bytes = std.math.mul(
221                 usize,
222                 self.xsize_value,
223                 @sizeOf(T),
224             ) catch return error.RowSizeOverflow;
225             const initialize_size = switch (mode) {
226                 .round_up => try roundUp(valid_bytes, vector_size),
227                 .unaligned => std.math.add(
228                     usize,
229                     valid_bytes,
230                     vector_size - @sizeOf(T),
231                 ) catch return error.RowSizeOverflow,
232             };
233             if (initialize_size > self.bytes_per_row_value) {
234                 return error.InsufficientRowPadding;
235             }
236             for (0..self.ysize_value) |y| {
237                 const begin = y * self.bytes_per_row_value + valid_bytes;
238                 @memset(self.storage[begin..][0 .. initialize_size - valid_bytes], 0);
239             }
240         }
241     };
242 }
243 
244 pub const ImageF = Image(f32);
245 
246 pub fn Image3(comptime T: type) type {
247     const ImageT = Image(T);
248     return struct {
249         planes: [3]ImageT,
250         packed_storage: []u8,
251         packing: Packing,
252 
253         const Self = @This();
254         pub const num_planes: usize = 3;
255 
256         pub fn empty() Self {
257             return .{
258                 .planes = .{ ImageT.empty(), ImageT.empty(), ImageT.empty() },
259                 .packed_storage = &.{},
260                 .packing = .none,
261             };
262         }
263 
264         pub fn init(allocator: std.mem.Allocator, width: usize, height: usize) Error!Self {
265             try validateDimensions(width, height);
266             if (width == 0 or height == 0) {
267                 return .{
268                     .planes = .{
269                         ImageT.zeroDimensions(width, height),
270                         ImageT.zeroDimensions(width, height),
271                         ImageT.zeroDimensions(width, height),
272                     },
273                     .packed_storage = &.{},
274                     .packing = .none,
275                 };
276             }
277             const stride = try bytesPerRowFor(width, @sizeOf(T), vector_size);
278             const plane_size = std.math.mul(usize, stride, height) catch
279                 return error.AllocationSizeOverflow;
280             const allocation_size = std.math.mul(usize, plane_size, num_planes) catch
281                 return error.AllocationSizeOverflow;
282             const storage = try allocator.alignedAlloc(
283                 u8,
284                 .fromByteUnits(storage_alignment),
285                 allocation_size,
286             );
287             errdefer allocator.free(storage);
288             var result = empty();
289             result.packed_storage = storage;
290             result.packing = .owned;
291             for (&result.planes, 0..) |*current_plane, index| {
292                 const begin = index * plane_size;
293                 current_plane.* = try ImageT.initBorrowed(
294                     width,
295                     height,
296                     stride,
297                     storage[begin..][0..plane_size],
298                 );
299                 try current_plane.initializePadding(.round_up);
300             }
301             return result;
302         }
303 
304         pub fn initBorrowed(
305             width: usize,
306             height: usize,
307             stride: usize,
308             buffers: [num_planes][]u8,
309         ) GeometryError!Self {
310             var result = empty();
311             for (&result.planes, buffers) |*current_plane, buffer| {
312                 current_plane.* = try ImageT.initBorrowed(width, height, stride, buffer);
313             }
314             return result;
315         }
316 
317         pub fn initPlanes(
318             plane0: *ImageT,
319             plane1: *ImageT,
320             plane2: *ImageT,
321         ) GeometryError!Self {
322             if (plane0 == plane1 or plane0 == plane2 or plane1 == plane2) {
323                 return error.AliasedPlanes;
324             }
325             if (!sameSize(plane0, plane1) or !sameSize(plane0, plane2)) {
326                 return error.PlaneSizeMismatch;
327             }
328             if (plane0.bytesPerRow() != plane1.bytesPerRow() or
329                 plane0.bytesPerRow() != plane2.bytesPerRow())
330             {
331                 return error.PlaneStrideMismatch;
332             }
333             const result = Self{
334                 .planes = .{ plane0.*, plane1.*, plane2.* },
335                 .packed_storage = &.{},
336                 .packing = .none,
337             };
338             plane0.* = ImageT.empty();
339             plane1.* = ImageT.empty();
340             plane2.* = ImageT.empty();
341             return result;
342         }
343 
344         pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
345             switch (self.packing) {
346                 .owned => {
347                     std.debug.assert(self.packed_storage.len != 0);
348                     const aligned: []align(storage_alignment) u8 = @alignCast(self.packed_storage);
349                     allocator.free(aligned);
350                 },
351                 .none => for (&self.planes) |*current_plane| current_plane.deinit(allocator),
352             }
353             self.* = empty();
354         }
355 
356         pub fn swap(self: *Self, other: *Self) void {
357             std.mem.swap(Self, self, other);
358         }
359 
360         pub fn shrinkTo(self: *Self, width: usize, height: usize) GeometryError!void {
361             if (width > self.xsize() or height > self.ysize()) {
362                 return error.InvalidShrink;
363             }
364             for (&self.planes) |*current_plane| try current_plane.shrinkTo(width, height);
365         }
366 
367         pub fn xsize(self: *const Self) usize {
368             return self.planes[0].xsize();
369         }
370 
371         pub fn ysize(self: *const Self) usize {
372             return self.planes[0].ysize();
373         }
374 
375         pub fn bytesPerRow(self: *const Self) usize {
376             return self.planes[0].bytesPerRow();
377         }
378 
379         pub fn pixelsPerRow(self: *const Self) usize {
380             return self.planes[0].pixelsPerRow();
381         }
382 
383         pub fn plane(self: *const Self, index: usize) *const ImageT {
384             std.debug.assert(index < num_planes);
385             return &self.planes[index];
386         }
387 
388         pub fn constPlaneRow(self: *const Self, component: usize, y: usize) []const T {
389             std.debug.assert(component < num_planes);
390             return self.planes[component].constRow(y);
391         }
392 
393         pub fn mutablePlaneRow(self: *const Self, component: usize, y: usize) []T {
394             std.debug.assert(component < num_planes);
395             return self.planes[component].mutableRow(y);
396         }
397     };
398 }
399 
400 pub const Image3F = Image3(f32);
401 
402 pub const Rect = struct {
403     x0_value: usize,
404     y0_value: usize,
405     xsize_value: usize,
406     ysize_value: usize,
407 
408     pub fn empty() Rect {
409         return init(0, 0, 0, 0);
410     }
411 
412     pub fn init(xbegin: usize, ybegin: usize, width: usize, height: usize) Rect {
413         return .{
414             .x0_value = xbegin,
415             .y0_value = ybegin,
416             .xsize_value = width,
417             .ysize_value = height,
418         };
419     }
420 
421     pub fn initClamped(
422         xbegin: usize,
423         ybegin: usize,
424         xsize_max: usize,
425         ysize_max: usize,
426         xend: usize,
427         yend: usize,
428     ) Rect {
429         return .{
430             .x0_value = xbegin,
431             .y0_value = ybegin,
432             .xsize_value = clampedSize(xbegin, xsize_max, xend),
433             .ysize_value = clampedSize(ybegin, ysize_max, yend),
434         };
435     }
436 
437     pub fn fromImage(image: anytype) Rect {
438         return init(0, 0, image.xsize(), image.ysize());
439     }
440 
441     pub fn subrect(
442         self: Rect,
443         xbegin: usize,
444         ybegin: usize,
445         xsize_max: usize,
446         ysize_max: usize,
447     ) GeometryError!Rect {
448         const absolute_x = std.math.add(usize, self.x0_value, xbegin) catch
449             return error.RowSizeOverflow;
450         const absolute_y = std.math.add(usize, self.y0_value, ybegin) catch
451             return error.RowSizeOverflow;
452         const xend = std.math.add(usize, self.x0_value, self.xsize_value) catch
453             return error.RowSizeOverflow;
454         const yend = std.math.add(usize, self.y0_value, self.ysize_value) catch
455             return error.RowSizeOverflow;
456         return initClamped(
457             absolute_x,
458             absolute_y,
459             xsize_max,
460             ysize_max,
461             xend,
462             yend,
463         );
464     }
465 
466     pub fn isInside(self: Rect, image: anytype) bool {
467         const xend = std.math.add(usize, self.x0_value, self.xsize_value) catch
468             return false;
469         const yend = std.math.add(usize, self.y0_value, self.ysize_value) catch
470             return false;
471         return xend <= image.xsize() and yend <= image.ysize();
472     }
473 
474     pub fn constRow(self: Rect, comptime T: type, image: *const Image(T), y: usize) []const T {
475         std.debug.assert(self.isInside(image));
476         std.debug.assert(y < self.ysize_value);
477         return image.constRow(y + self.y0_value)[self.x0_value..];
478     }
479 
480     pub fn mutableRow(self: Rect, comptime T: type, image: *const Image(T), y: usize) []T {
481         std.debug.assert(self.isInside(image));
482         std.debug.assert(y < self.ysize_value);
483         return image.mutableRow(y + self.y0_value)[self.x0_value..];
484     }
485 
486     pub fn constPlaneRow(
487         self: Rect,
488         comptime T: type,
489         image: *const Image3(T),
490         component: usize,
491         y: usize,
492     ) []const T {
493         std.debug.assert(self.isInside(image));
494         std.debug.assert(y < self.ysize_value);
495         return image.constPlaneRow(component, y + self.y0_value)[self.x0_value..];
496     }
497 
498     pub fn mutablePlaneRow(
499         self: Rect,
500         comptime T: type,
501         image: *const Image3(T),
502         component: usize,
503         y: usize,
504     ) []T {
505         std.debug.assert(self.isInside(image));
506         std.debug.assert(y < self.ysize_value);
507         return image.mutablePlaneRow(component, y + self.y0_value)[self.x0_value..];
508     }
509 
510     pub fn x0(self: Rect) usize {
511         return self.x0_value;
512     }
513 
514     pub fn y0(self: Rect) usize {
515         return self.y0_value;
516     }
517 
518     pub fn xsize(self: Rect) usize {
519         return self.xsize_value;
520     }
521 
522     pub fn ysize(self: Rect) usize {
523         return self.ysize_value;
524     }
525 };
526 
527 pub fn sameSize(first: anytype, second: anytype) bool {
528     return first.xsize() == second.xsize() and first.ysize() == second.ysize();
529 }
530 
531 pub fn mirror(coord: i64, size: usize) usize {
532     std.debug.assert(size != 0);
533     std.debug.assert(size <= std.math.maxInt(i64));
534     const size_u64: u64 = @intCast(size);
535     const period = 2 * size_u64;
536     const phase = if (coord >= 0)
537         @as(u64, @intCast(coord)) % period
538     else blk: {
539         const bits: u64 = @bitCast(coord);
540         const remainder = (0 -% bits) % period;
541         break :blk if (remainder == 0) 0 else period - remainder;
542     };
543     if (phase < size_u64) return @intCast(phase);
544     return @intCast(period - 1 - phase);
545 }
546 
547 pub const WrapMirror = struct {
548     pub fn call(_: WrapMirror, coord: i64, size: usize) usize {
549         return mirror(coord, size);
550     }
551 };
552 
553 pub const WrapUnchanged = struct {
554     pub fn call(_: WrapUnchanged, coord: i64, size: usize) usize {
555         std.debug.assert(coord >= 0);
556         std.debug.assert(coord < size);
557         return @intCast(coord);
558     }
559 };
560 
561 pub const WrapRowMirror = struct {
562     first_row: [*]const f32,
563     last_row: [*]const f32,
564 
565     pub fn init(image: anytype, ysize: usize) WrapRowMirror {
566         std.debug.assert(ysize != 0);
567         return .{
568             .first_row = image.constRow(0).ptr,
569             .last_row = image.constRow(ysize - 1).ptr,
570         };
571     }
572 
573     pub fn call(self: WrapRowMirror, row: [*]const f32, stride: i64) [*]const f32 {
574         std.debug.assert(stride > 0);
575         const first = @intFromPtr(self.first_row);
576         const last = @intFromPtr(self.last_row);
577         const address = @intFromPtr(row);
578         const stride_elements: usize = @intCast(stride);
579         if (address < first) {
580             const distance_bytes = first - address;
581             std.debug.assert(distance_bytes % @sizeOf(f32) == 0);
582             const distance = distance_bytes / @sizeOf(f32);
583             std.debug.assert(distance >= stride_elements);
584             return self.first_row + (distance - stride_elements);
585         }
586         if (address > last) {
587             const distance_bytes = address - last;
588             std.debug.assert(distance_bytes % @sizeOf(f32) == 0);
589             const distance = distance_bytes / @sizeOf(f32);
590             std.debug.assert(distance >= stride_elements);
591             return self.last_row - (distance - stride_elements);
592         }
593         return row;
594     }
595 };
596 
597 pub const WrapRowUnchanged = struct {
598     pub fn call(_: WrapRowUnchanged, row: [*]const f32, _: i64) [*]const f32 {
599         return row;
600     }
601 };
602 
603 fn validateComponent(comptime T: type) void {
604     if (@sizeOf(T) != 1 and @sizeOf(T) != 2 and @sizeOf(T) != 4 and @sizeOf(T) != 8) {
605         @compileError("Highway images require 1/2/4/8-byte component types");
606     }
607     if (@typeInfo(T) == .pointer or @typeInfo(T) == .optional) {
608         @compileError("Highway images require plain component values");
609     }
610 }
611 
612 fn validateGeometry(component_size: usize, selected_vector_size: usize) GeometryError!void {
613     if (component_size != 1 and component_size != 2 and
614         component_size != 4 and component_size != 8)
615     {
616         return error.InvalidComponentSize;
617     }
618     if (!std.math.isPowerOfTwo(selected_vector_size)) return error.InvalidVectorSize;
619     if (selected_vector_size != 1 and selected_vector_size < component_size) {
620         return error.InvalidVectorSize;
621     }
622 }
623 
624 fn validateDimensions(xsize: usize, ysize: usize) GeometryError!void {
625     if (xsize > std.math.maxInt(u32) or ysize > std.math.maxInt(u32)) {
626         return error.DimensionTooLarge;
627     }
628 }
629 
630 fn roundUp(value: usize, alignment: usize) GeometryError!usize {
631     std.debug.assert(std.math.isPowerOfTwo(alignment));
632     const mask = alignment - 1;
633     const biased = std.math.add(usize, value, mask) catch return error.RowSizeOverflow;
634     return biased & ~mask;
635 }
636 
637 fn clampedSize(begin: usize, size_max: usize, end: usize) usize {
638     if (end <= begin) return 0;
639     return @min(size_max, end - begin);
640 }