summaryrefslogtreecommitdiff
path: root/src/estd
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2025-02-12 12:48:11 +0100
committerNathan Reiner <nathan@nathanreiner.xyz>2025-02-12 12:48:11 +0100
commit9fd81c0b38b2b843c24fb61bf8cb5b7873deaa72 (patch)
treea2d4d76a4fcc1334d83c5538e684061913be24d3 /src/estd
parentdae5bc02b1c934075e95694953b4330676e21611 (diff)
graphics: add line
Diffstat (limited to 'src/estd')
-rw-r--r--src/estd/graphics/box.zig101
-rw-r--r--src/estd/graphics/canvas.zig13
-rw-r--r--src/estd/graphics/color.zig23
-rw-r--r--src/estd/graphics/curve.zig36
-rw-r--r--src/estd/graphics/line.zig76
-rw-r--r--src/estd/graphics/root.zig202
6 files changed, 254 insertions, 197 deletions
diff --git a/src/estd/graphics/box.zig b/src/estd/graphics/box.zig
new file mode 100644
index 0000000..83868aa
--- /dev/null
+++ b/src/estd/graphics/box.zig
@@ -0,0 +1,101 @@
+const std = @import("std");
+const graphics = @import("root.zig");
+
+pub const Box = struct {
+ const Self = @This();
+
+ x: u32,
+ y: u32,
+ width: u32,
+ height: u32,
+ color: graphics.Color,
+ radius: u32 = 0,
+
+ fn render_rect(
+ self: *const Self,
+ x: u32,
+ y: u32,
+ width: u32,
+ height: u32,
+ canvas: *const graphics.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 graphics.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(@round(std.math.sqrt(@as(f64, @floatFromInt(radius * radius - yp * yp)))));
+ 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
+ );
+ }
+ }
+};
diff --git a/src/estd/graphics/canvas.zig b/src/estd/graphics/canvas.zig
new file mode 100644
index 0000000..35dba4b
--- /dev/null
+++ b/src/estd/graphics/canvas.zig
@@ -0,0 +1,13 @@
+const graphics = @import("root.zig");
+
+pub const Canvas = struct {
+ const Self = @This();
+
+ width: u32,
+ height: u32,
+ buffer: []volatile graphics.Color,
+
+ pub fn fill(self: *const Canvas, color: graphics.Color) void {
+ @memset(self.buffer, color);
+ }
+};
diff --git a/src/estd/graphics/color.zig b/src/estd/graphics/color.zig
new file mode 100644
index 0000000..b7fd97e
--- /dev/null
+++ b/src/estd/graphics/color.zig
@@ -0,0 +1,23 @@
+pub const Color = packed struct(u32) {
+ const Self = @This();
+
+ blue: u8,
+ green: u8,
+ red: u8,
+ _padding: u8 = 0x0,
+
+ pub fn mix(self: *const Self, other: *const Self, factor: f64) Self {
+ const inverse = 1 - factor;
+ return Self {
+ .red = @intFromFloat(
+ @as(f64, @floatFromInt(self.red)) * inverse + @as(f64, @floatFromInt(other.red)) * factor
+ ),
+ .green = @intFromFloat(
+ @as(f64, @floatFromInt(self.green)) * inverse + @as(f64, @floatFromInt(other.green)) * factor
+ ),
+ .blue = @intFromFloat(
+ @as(f64, @floatFromInt(self.blue)) * inverse + @as(f64, @floatFromInt(other.blue)) * factor
+ ),
+ };
+ }
+};
diff --git a/src/estd/graphics/curve.zig b/src/estd/graphics/curve.zig
new file mode 100644
index 0000000..d4be1d7
--- /dev/null
+++ b/src/estd/graphics/curve.zig
@@ -0,0 +1,36 @@
+const std = @import("std");
+const graphics = @import("root.zig");
+
+pub const Curve = struct {
+ const Self = @This();
+ const Point = struct {
+ x: u32,
+ y: u32,
+ };
+
+ start: Point,
+ middle: Point,
+ end: Point,
+
+ color: graphics.Color,
+
+ pub fn render(self: *const Self, canvas: *const graphics.Canvas) void {
+ const start_x: f64 = @floatFromInt(self.start.x);
+ const start_y: f64 = @floatFromInt(self.start.y);
+ const middle_x: f64 = @floatFromInt(self.middle.x);
+ const middle_y: f64 = @floatFromInt(self.middle.y);
+ const end_x: f64 = @floatFromInt(self.end.x);
+ const end_y: f64 = @floatFromInt(self.end.y);
+
+ for (0..100) |index| {
+ const t: f64 = @as(f64, @floatFromInt(index)) / 100;
+ const x: u32 = @intFromFloat(@round(
+ (1 - t) * ((1 - t) * start_x + t * middle_x) + t * ((1 - t) * middle_x + t * end_x)
+ ));
+ const y: u32 = @intFromFloat(@round(
+ (1 - t) * ((1 - t) * start_y + t * middle_y) + t * ((1 - t) * middle_y + t * end_y)
+ ));
+ canvas.buffer[x + canvas.width * y] = self.color;
+ }
+ }
+};
diff --git a/src/estd/graphics/line.zig b/src/estd/graphics/line.zig
new file mode 100644
index 0000000..93f17ff
--- /dev/null
+++ b/src/estd/graphics/line.zig
@@ -0,0 +1,76 @@
+const std = @import("std");
+const graphics = @import("root.zig");
+
+pub const Line = struct {
+ const Self = @This();
+ const Point = struct {
+ x: u32,
+ y: u32,
+ };
+
+ start: Point,
+ end: Point,
+
+ color: graphics.Color,
+
+ fn distance(a: u32, b: u32) u32 {
+ if (a < b) {
+ return b - a;
+ } else {
+ return a - b;
+ }
+ }
+
+ pub fn render(self: *const Self, canvas: *const graphics.Canvas) void {
+ var start = self.start;
+ var end = self.end;
+ const steep = distance(start.y, end.y) > distance(start.x, end.x);
+
+ if (steep) {
+ std.mem.swap(u32, &start.x, &start.y);
+ std.mem.swap(u32, &end.x, &end.y);
+ }
+
+ if (start.x > end.x) {
+ std.mem.swap(Point, &start, &end);
+ }
+
+ const dx: f64 = @floatFromInt(end.x - start.x);
+ const dy: f64 = @floatFromInt(@as(i64, @intCast(end.y)) - @as(i64, @intCast(start.y)));
+ var gradient: f64 = dy / dx;
+ if (dx == 0) {
+ gradient = 1;
+ }
+
+
+ var intersect_y: f64 = @floatFromInt(start.y);
+
+ if (steep) {
+ for (start.x..end.x + 1) |x| {
+ const y: usize = @intFromFloat(intersect_y);
+ const factor = intersect_y - @as(f64, @floatFromInt(y));
+
+ var previous_color = canvas.buffer[canvas.width * x + y];
+ canvas.buffer[x * canvas.width + y] = previous_color.mix(&self.color, factor);
+
+ previous_color = canvas.buffer[x * canvas.width + y - 1];
+ canvas.buffer[x * canvas.width + y - 1] = self.color.mix(&previous_color, factor);
+
+ intersect_y += gradient;
+ }
+ } else {
+ for (start.x..end.x + 1) |x| {
+ const y: u32 = @intFromFloat(intersect_y);
+ const factor = intersect_y - @as(f64, @floatFromInt(y));
+
+ var previous_color = canvas.buffer[x + canvas.width * y];
+ canvas.buffer[x + canvas.width * y] = previous_color.mix(&self.color, factor);
+
+ previous_color = canvas.buffer[x + canvas.width * (y - 1)];
+ canvas.buffer[x + canvas.width * (y - 1)] = self.color.mix(&previous_color, factor);
+
+ intersect_y += gradient;
+ }
+ }
+ }
+};
diff --git a/src/estd/graphics/root.zig b/src/estd/graphics/root.zig
index da09450..533d518 100644
--- a/src/estd/graphics/root.zig
+++ b/src/estd/graphics/root.zig
@@ -1,197 +1,5 @@
-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));
- }
- }
-};
+pub const Canvas = @import("canvas.zig").Canvas;
+pub const Box = @import("box.zig").Box;
+pub const Color = @import("color.zig").Color;
+pub const Line = @import("line.zig").Line;
+pub const Curve = @import("curve.zig").Curve;