Ndarray - N-Dimensional Arrays

I’ve created a module for working with n-dimensional arrays and slices of those arrays. The module has evolved over multiple years, and now I’ve gotten to place where I’m happy with the API.

You can find the module under the klaji organization on codeberg.org:

The core types of this module are ndarray.MutSlice(D, T) and ndarray.ConstSlice(D, T), where D is the number of dimensions and T is the child type. From there you can get a pointer to a single element using the at() function, get a regular slice of elements using row(), or get a slice of a sub-dimension using idx():

const ndarray = @import("ndarray");
const std = @import("std");

pub fn main() !void {
    const b: ConstSlice(2, u8) = .fromFlatSlice(.{ 3, 2 }, &[_]u8{
        11, 12,
        21, 22,
        31, 32,
    });
    try std.testing.expectEqual(@as(u8, 32), b.at(.{ 2, 1 }).*);
    try std.testing.expectEqual(@as(u8, 32), b.idx(2).idx(1).*);

    try std.testing.expectEqualSlices(u8, &.{ 11, 12 }, b.row(.{0}));
    try std.testing.expectEqualSlices(u8, &.{ 21, 22 }, b.row(.{1}));
    try std.testing.expectEqualSlices(u8, &.{ 31, 32 }, b.row(.{2}));
}

You can use slice() and set() to fill in rectangular regions with a specific value.

test "slice" {
    var result = try MutSlice(2, f32).alloc(std.testing.allocator, .{ 5, 5 });
    defer result.free(std.testing.allocator);

    result.set(0);
    try result.expectEqual(
        .fromFlatSlice(.{ 5, 5 }, &.{
            0, 0, 0, 0, 0,
            0, 0, 0, 0, 0,
            0, 0, 0, 0, 0,
            0, 0, 0, 0, 0,
            0, 0, 0, 0, 0,
        }),
    );

    {
        const s = result.slice(.{ 1, 1 }, .{ 4, 4 });
        s.set(1);
    }
    try result.expectEqual(
        .fromFlatSlice(.{ 5, 5 }, &.{
            0, 0, 0, 0, 0,
            0, 1, 1, 1, 0,
            0, 1, 1, 1, 0,
            0, 1, 1, 1, 0,
            0, 0, 0, 0, 0,
        }),
    );

    {
        const s = result.slice(.{ 2, 2 }, .{ 3, 3 });
        s.set(2);
    }
    try result.expectEqual(
        .fromFlatSlice(.{ 5, 5 }, &.{
            0, 0, 0, 0, 0,
            0, 1, 1, 1, 0,
            0, 1, 2, 1, 0,
            0, 1, 1, 1, 0,
            0, 0, 0, 0, 0,
        }),
    );
}

The module also has basic support for reading and writing PNG files. Here’s an example that creates the following image:

concentric

pub fn main() !void {
    var debug_allocator = std.heap.DebugAllocator(.{}){};
    defer _ = debug_allocator.deinit();
    const gpa = debug_allocator.allocator();

    var result = try ndarray.MutSlice(2, u8).alloc(gpa, .{ 5 * 16, 5 * 16 });
    defer result.free(gpa);

    result.set(0);
    result.slice(.{ 1 * 16, 1 * 16 }, .{ 4 * 16, 4 * 16 }).set(1);
    result.slice(.{ 2 * 16, 2 * 16 }, .{ 3 * 16, 3 * 16 }).set(2);

    var file = try std.fs.cwd().createFile("concentric.png", .{ .exclusive = false });
    var write_buffer: [4096]u8 = undefined;
    var writer = file.writerStreaming(write_buffer[0..]);

    try ndarray.file_format.png.write(
        .{
            .palette = &.{
                .{ .r = 0, .g = 0, .b = 0 },
                .{ .r = 0xff, .g = 0xff, .b = 0xff },
                .{ .r = 0xff, .g = 0, .b = 0 },
            },
            .pixels = .{ .indexed8 = result },
        },
        gpa,
        &writer.interface,
        .{},
    );

    try writer.interface.flush();
}

const ndarray = @import("ndarray");
const std = @import("std");

Note that the PNG writing is uncompressed; the produced files will be much larger that necessary.

3 Likes