diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-02-07 20:39:58 +0100 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-02-07 20:39:58 +0100 |
| commit | dae5bc02b1c934075e95694953b4330676e21611 (patch) | |
| tree | faa1a80849e5642d0b4bd8b4a91331b1da5b75bf /src/estd | |
| parent | fef523a8d7c87f272de18c8abd57e0cc53e2ef40 (diff) | |
estd: add graphics module
Diffstat (limited to 'src/estd')
| -rw-r--r-- | src/estd/graphics/root.zig | 197 | ||||
| -rw-r--r-- | src/estd/root.zig | 1 |
2 files changed, 198 insertions, 0 deletions
diff --git a/src/estd/graphics/root.zig b/src/estd/graphics/root.zig new file mode 100644 index 0000000..da09450 --- /dev/null +++ b/src/estd/graphics/root.zig @@ -0,0 +1,197 @@ +const std = @import("std"); + +pub const Pixel = packed struct(u32) { + blue: u8, + green: u8, + red: u8, + _padding: u8 = 0x0, +}; + +pub const Canvas = struct { + const Self = @This(); + + width: u32, + height: u32, + buffer: []volatile Pixel, + + pub fn fill(self: *const Canvas, pixel: Pixel) void { + @memset(self.buffer, pixel); + } + + fn gaussian_mean(values: [9]u32) u32 { + return (values[0] + values[1] * 2 + values[2] + + values[3] * 2 + values[4] * 8 + values[5] * 2 + + values[6] + values[7] * 2 + values[8]) / 20; + } + + pub fn set_with_anti_aliasing(self: *const Canvas, x: u32, y: u32) void { + var neighbors_red: [9]u32 = undefined; + var neighbors_green: [9]u32 = undefined; + var neighbors_blue: [9]u32 = undefined; + + for (0..3) |h| { + for (0..3) |w| { + const pixel = self.buffer[(y + 1 - h) * self.width + x + 1 - w]; + neighbors_red[w + 3 * h] = pixel.red; + neighbors_green[w + 3 * h] = pixel.green; + neighbors_blue[w + 3 * h] = pixel.blue; + } + } + + self.buffer[x + (y * self.width)] = .{ + .red = @intCast(Self.gaussian_mean(neighbors_red)), + .green = @intCast(Self.gaussian_mean(neighbors_green)), + .blue = @intCast(Self.gaussian_mean(neighbors_blue)), + }; + } +}; + +pub const Box = struct { + const Self = @This(); + + x: u32, + y: u32, + width: u32, + height: u32, + color: Pixel, + radius: u32 = 0, + + fn render_rect( + self: *const Self, + x: u32, + y: u32, + width: u32, + height: u32, + canvas: *const Canvas + ) void { + if (x >= canvas.width or y >= canvas.height) { + return; + } + + const x_end = @min(x + width, canvas.width); + const y_end = @min(y + height, canvas.height); + + for (y..y_end) |yp| { + const offset = yp * canvas.width; + @memset( + canvas.buffer[(offset + x)..(offset + x_end)], + self.color + ); + } + } + + pub fn render(self: *const Self, canvas: *const Canvas) void { + if (self.radius == 0) { + self.render_rect(self.x, self.y, self.width, self.height, canvas); + return; + } + + const radius = @min(self.radius, self.width / 2, self.height / 2); + const core_width = self.width - 2 * radius; + const core_height = self.height - 2 * radius; + + self.render_rect( + self.x + radius, + self.y, + core_width, + radius, + canvas + ); + + self.render_rect( + self.x + radius, + self.y + radius + core_height, + core_width, + radius, + canvas + ); + + self.render_rect( + self.x, + self.y + radius, + self.width, + core_height, + canvas + ); + + for (0..radius) |yp| { + const width: u32 = @intFromFloat(std.math.round( + @as(f64, @floatFromInt(radius)) * + @cos(std.math.asin( + @as(f64, @floatFromInt(yp)) / @as(f64, @floatFromInt(radius)) + )) + )); + const spacing = radius - width; + + var offset = ((radius - yp) + self.y) * canvas.width + self.x + spacing; + @memset( + canvas.buffer[offset..offset + width], + self.color + ); + + offset = ((radius - yp) + self.y) * canvas.width + self.x + core_width + radius; + @memset( + canvas.buffer[offset..offset + width], + self.color + ); + + offset = (yp + self.y + radius + core_height) * canvas.width + self.x + spacing; + @memset( + canvas.buffer[offset..offset + width], + self.color + ); + + offset = (yp + self.y + radius + core_height) * canvas.width + self.x + core_width + radius; + @memset( + canvas.buffer[offset..offset + width], + self.color + ); + } + + for (0..radius) |yp| { + const width: u32 = @intFromFloat(std.math.round( + @as(f64, @floatFromInt(radius)) * + @cos(std.math.asin( + @as(f64, @floatFromInt(yp)) / @as(f64, @floatFromInt(radius)) + )) + )); + + const spacing = radius - width; + + canvas.set_with_anti_aliasing( + self.x + spacing, + @intCast(self.y + radius - yp), + ); + + canvas.set_with_anti_aliasing( + self.x + spacing - 1, + @intCast(self.y + radius - yp), + ); + + canvas.set_with_anti_aliasing( + self.x + core_width + radius + width, + @intCast(self.y + radius - yp), + ); + + canvas.set_with_anti_aliasing( + self.x + spacing, + @intCast(self.y + yp + radius + core_height), + ); + + canvas.set_with_anti_aliasing( + self.x + core_width + radius + width, + @intCast(self.y + yp + radius + core_height), + ); + } + + for (0..core_width) |w| { + canvas.set_with_anti_aliasing(@intCast(self.x + radius + w), self.y); + canvas.set_with_anti_aliasing(@intCast(self.x + radius + w), self.y + self.height); + } + + for (0..core_height) |h| { + canvas.set_with_anti_aliasing(self.x, @intCast(self.y + radius + h)); + canvas.set_with_anti_aliasing(self.x + self.width, @intCast(self.y + radius + h)); + } + } +}; diff --git a/src/estd/root.zig b/src/estd/root.zig index 00c2631..5ff292e 100644 --- a/src/estd/root.zig +++ b/src/estd/root.zig @@ -2,6 +2,7 @@ const std = @import("std"); pub const cursor = @import("cursor.zig"); pub const parser = @import("parser/root.zig"); +pub const graphics = @import("graphics/root.zig"); test { std.testing.refAllDecls(@This()); |