summaryrefslogtreecommitdiff
path: root/src/estd
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2025-02-07 20:39:58 +0100
committerNathan Reiner <nathan@nathanreiner.xyz>2025-02-07 20:39:58 +0100
commitdae5bc02b1c934075e95694953b4330676e21611 (patch)
treefaa1a80849e5642d0b4bd8b4a91331b1da5b75bf /src/estd
parentfef523a8d7c87f272de18c8abd57e0cc53e2ef40 (diff)
estd: add graphics module
Diffstat (limited to 'src/estd')
-rw-r--r--src/estd/graphics/root.zig197
-rw-r--r--src/estd/root.zig1
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());