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)); } } };