diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-02-12 12:48:11 +0100 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-02-12 12:48:11 +0100 |
| commit | 9fd81c0b38b2b843c24fb61bf8cb5b7873deaa72 (patch) | |
| tree | a2d4d76a4fcc1334d83c5538e684061913be24d3 /src/estd/graphics | |
| parent | dae5bc02b1c934075e95694953b4330676e21611 (diff) | |
graphics: add line
Diffstat (limited to 'src/estd/graphics')
| -rw-r--r-- | src/estd/graphics/box.zig | 101 | ||||
| -rw-r--r-- | src/estd/graphics/canvas.zig | 13 | ||||
| -rw-r--r-- | src/estd/graphics/color.zig | 23 | ||||
| -rw-r--r-- | src/estd/graphics/curve.zig | 36 | ||||
| -rw-r--r-- | src/estd/graphics/line.zig | 76 | ||||
| -rw-r--r-- | src/estd/graphics/root.zig | 202 |
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; |