font/sprite: address PR review feedback

- Make canvas geometry primitives generic, use `Rect(u32)` for `rect`
function, so that we don't have to worry about negatives or rounding.
- Make `Quads` struct packed just in case it gets non-comptime use in
the future.
- Clarify comment on why we're discarding out of range pixels + runtime
unreachable for any other type of error which we shouldn't ever see.
- Move z2d import above in-tree imports.
This commit is contained in:
Qwerasd
2024-10-15 11:59:52 -04:00
parent 83a56afcb1
commit c66042d6e0
4 changed files with 120 additions and 100 deletions

View File

@ -20,11 +20,11 @@ const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const z2d = @import("z2d");
const font = @import("../main.zig");
const Sprite = @import("../sprite.zig").Sprite;
const z2d = @import("z2d");
const log = std.log.scoped(.box_font);
/// The cell width and height because the boxes are fit perfectly
@ -72,7 +72,7 @@ const Lines = packed struct(u8) {
/// Specification of a quadrants char, which has each of the
/// 4 quadrants of the character cell either filled or empty.
const Quads = struct {
const Quads = packed struct(u4) {
tl: bool = false,
tr: bool = false,
bl: bool = false,
@ -1621,10 +1621,10 @@ fn draw_block_shade(
};
canvas.rect(.{
.x = @floatFromInt(x),
.y = @floatFromInt(y),
.width = @floatFromInt(w),
.height = @floatFromInt(h),
.x = x,
.y = y,
.width = w,
.height = h,
}, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade))));
}
@ -1671,10 +1671,10 @@ fn draw_checkerboard_fill(self: Box, canvas: *font.sprite.Canvas, parity: u1) vo
const y1 = (self.height * (y + 1)) / y_size;
if ((x + y) % 2 == parity) {
canvas.rect(.{
.x = @floatFromInt(x0),
.y = @floatFromInt(y0),
.width = @floatFromInt(x1 -| x0),
.height = @floatFromInt(y1 -| y0),
.x = @intCast(x0),
.y = @intCast(y0),
.width = @intCast(x1 -| x0),
.height = @intCast(y1 -| y0),
}, .on);
}
}
@ -1841,8 +1841,8 @@ fn draw_circle(
fn draw_line(
self: Box,
canvas: *font.sprite.Canvas,
p0: font.sprite.Point,
p1: font.sprite.Point,
p0: font.sprite.Point(f64),
p1: font.sprite.Point(f64),
comptime thickness: Thickness,
) !void {
canvas.line(
@ -1853,11 +1853,11 @@ fn draw_line(
}
fn draw_shade(self: Box, canvas: *font.sprite.Canvas, v: u16) void {
canvas.rect((font.sprite.Box{
canvas.rect((font.sprite.Box(u32){
.p0 = .{ .x = 0, .y = 0 },
.p1 = .{
.x = @floatFromInt(self.width),
.y = @floatFromInt(self.height),
.x = self.width,
.y = self.height,
},
}).rect(), @as(font.sprite.Color, @enumFromInt(v)));
}
@ -2395,12 +2395,12 @@ fn vline(
x: u32,
thickness_px: u32,
) void {
canvas.rect((font.sprite.Box{ .p0 = .{
.x = @floatFromInt(@min(@max(x, 0), self.width)),
.y = @floatFromInt(@min(@max(y1, 0), self.height)),
canvas.rect((font.sprite.Box(u32){ .p0 = .{
.x = @min(@max(x, 0), self.width),
.y = @min(@max(y1, 0), self.height),
}, .p1 = .{
.x = @floatFromInt(@min(@max(x + thickness_px, 0), self.width)),
.y = @floatFromInt(@min(@max(y2, 0), self.height)),
.x = @min(@max(x + thickness_px, 0), self.width),
.y = @min(@max(y2, 0), self.height),
} }).rect(), .on);
}
@ -2412,12 +2412,12 @@ fn hline(
y: u32,
thickness_px: u32,
) void {
canvas.rect((font.sprite.Box{ .p0 = .{
.x = @floatFromInt(@min(@max(x1, 0), self.width)),
.y = @floatFromInt(@min(@max(y, 0), self.height)),
canvas.rect((font.sprite.Box(u32){ .p0 = .{
.x = @min(@max(x1, 0), self.width),
.y = @min(@max(y, 0), self.height),
}, .p1 = .{
.x = @floatFromInt(@min(@max(x2, 0), self.width)),
.y = @floatFromInt(@min(@max(y + thickness_px, 0), self.height)),
.x = @min(@max(x2, 0), self.width),
.y = @min(@max(y + thickness_px, 0), self.height),
} }).rect(), .on);
}
@ -2429,12 +2429,12 @@ fn rect(
x2: u32,
y2: u32,
) void {
canvas.rect((font.sprite.Box{ .p0 = .{
.x = @floatFromInt(@min(@max(x1, 0), self.width)),
.y = @floatFromInt(@min(@max(y1, 0), self.height)),
canvas.rect((font.sprite.Box(u32){ .p0 = .{
.x = @min(@max(x1, 0), self.width),
.y = @min(@max(y1, 0), self.height),
}, .p1 = .{
.x = @floatFromInt(@min(@max(x2, 0), self.width)),
.y = @floatFromInt(@min(@max(y2, 0), self.height)),
.x = @min(@max(x2, 0), self.width),
.y = @min(@max(y2, 0), self.height),
} }).rect(), .on);
}

View File

@ -391,8 +391,8 @@ fn draw_half_circle(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canv
const average = @as(u8, @intCast(@min(total / (supersample * supersample), 0xFF)));
canvas.rect(
.{
.x = @floatFromInt(c),
.y = @floatFromInt(r),
.x = @intCast(c),
.y = @intCast(r),
.width = 1,
.height = 1,
},
@ -404,7 +404,7 @@ fn draw_half_circle(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canv
}
fn draw_trapezoid_top_bottom(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void {
const t_top: Quad = if (cp == 0xE0D4)
const t_top: Quad(f64) = if (cp == 0xE0D4)
.{
.p0 = .{
.x = 0,
@ -443,7 +443,7 @@ fn draw_trapezoid_top_bottom(self: Powerline, canvas: *font.sprite.Canvas, cp: u
},
};
const t_bottom: Quad = if (cp == 0xE0D4)
const t_bottom: Quad(f64) = if (cp == 0xE0D4)
.{
.p0 = .{
.x = @floatFromInt(self.width - self.width / 3),

View File

@ -6,53 +6,66 @@ const Allocator = std.mem.Allocator;
const z2d = @import("z2d");
const font = @import("../main.zig");
pub const Point = struct {
x: f64,
y: f64,
};
pub fn Point(comptime T: type) type {
return struct {
x: T,
y: T,
};
}
pub const Line = struct {
p0: Point,
p1: Point,
};
pub fn Line(comptime T: type) type {
return struct {
p0: Point(T),
p1: Point(T),
};
}
pub const Box = struct {
p0: Point,
p1: Point,
pub fn Box(comptime T: type) type {
return struct {
p0: Point(T),
p1: Point(T),
pub fn rect(self: Box) Rect {
const tl_x = @min(self.p0.x, self.p1.x);
const tl_y = @min(self.p0.y, self.p1.y);
const br_x = @max(self.p0.x, self.p1.x);
const br_y = @max(self.p0.y, self.p1.y);
return .{
.x = tl_x,
.y = tl_y,
.width = br_x - tl_x,
.height = br_y - tl_y,
};
}
};
pub fn rect(self: Box(T)) Rect(T) {
const tl_x = @min(self.p0.x, self.p1.x);
const tl_y = @min(self.p0.y, self.p1.y);
const br_x = @max(self.p0.x, self.p1.x);
const br_y = @max(self.p0.y, self.p1.y);
pub const Rect = struct {
x: f64,
y: f64,
width: f64,
height: f64,
};
return .{
.x = tl_x,
.y = tl_y,
.width = br_x - tl_x,
.height = br_y - tl_y,
};
}
};
}
pub const Triangle = struct {
p0: Point,
p1: Point,
p2: Point,
};
pub fn Rect(comptime T: type) type {
return struct {
x: T,
y: T,
width: T,
height: T,
};
}
pub const Quad = struct {
p0: Point,
p1: Point,
p2: Point,
p3: Point,
};
pub fn Triangle(comptime T: type) type {
return struct {
p0: Point(T),
p1: Point(T),
p2: Point(T),
};
}
pub fn Quad(comptime T: type) type {
return struct {
p0: Point(T),
p1: Point(T),
p2: Point(T),
p3: Point(T),
};
}
/// We only use alpha-channel so a pixel can only be "on" or "off".
pub const Color = enum(u8) {
@ -137,19 +150,26 @@ pub const Canvas = struct {
@intCast(x),
@intCast(y),
.{ .alpha8 = .{ .a = @intFromEnum(color) } },
) catch {
// If we try to set out of range this will fail.
// We just silently ignore that.
) catch |e| switch (e) {
error.OutOfRange => {
// If we try to set out of range this will fail. We just silently
// ignore it, so that this method (and `rect` which uses it) have
// implicit bounds clipping.
},
else => {
std.log.err("Wtf? err={}", .{e});
unreachable; // This shouldn't be possible.
},
};
}
/// Draw and fill a rectangle. This is the main primitive for drawing
/// lines as well (which are just generally skinny rectangles...)
pub fn rect(self: *Canvas, v: Rect, color: Color) void {
const x0: usize = @intFromFloat(v.x);
const x1: usize = @intFromFloat(v.x + v.width);
const y0: usize = @intFromFloat(v.y);
const y1: usize = @intFromFloat(v.y + v.height);
pub fn rect(self: *Canvas, v: Rect(u32), color: Color) void {
const x0 = v.x;
const x1 = v.x + v.width;
const y0 = v.y;
const y1 = v.y + v.height;
for (y0..y1) |y| {
for (x0..x1) |x| {
@ -163,7 +183,7 @@ pub const Canvas = struct {
}
/// Draw and fill a quad.
pub fn quad(self: *Canvas, q: Quad, color: Color) !void {
pub fn quad(self: *Canvas, q: Quad(f64), color: Color) !void {
var ctx: z2d.Context = .{
.surface = self.sfc,
.pattern = .{
@ -186,7 +206,7 @@ pub const Canvas = struct {
}
/// Draw and fill a triangle.
pub fn triangle(self: *Canvas, t: Triangle, color: Color) !void {
pub fn triangle(self: *Canvas, t: Triangle(f64), color: Color) !void {
var ctx: z2d.Context = .{
.surface = self.sfc,
.pattern = .{
@ -208,7 +228,7 @@ pub const Canvas = struct {
}
/// Stroke a line.
pub fn line(self: *Canvas, l: Line, thickness: f64, color: Color) !void {
pub fn line(self: *Canvas, l: Line(f64), thickness: f64, color: Color) !void {
var ctx: z2d.Context = .{
.surface = self.sfc,
.pattern = .{

View File

@ -70,8 +70,8 @@ fn drawSingle(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset {
canvas.rect(.{
.x = 0,
.y = 0,
.width = @floatFromInt(width),
.height = @floatFromInt(thickness),
.width = width,
.height = thickness,
}, .on);
const offset_y: i32 = 0;
@ -91,15 +91,15 @@ fn drawDouble(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset {
canvas.rect(.{
.x = 0,
.y = 0,
.width = @floatFromInt(width),
.height = @floatFromInt(thickness),
.width = width,
.height = thickness,
}, .on);
canvas.rect(.{
.x = 0,
.y = @floatFromInt(thickness * 2),
.width = @floatFromInt(width),
.height = @floatFromInt(thickness),
.y = thickness * 2,
.width = width,
.height = thickness,
}, .on);
const offset_y: i32 = -@as(i32, @intCast(thickness));
@ -121,10 +121,10 @@ fn drawDotted(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset {
const x = @min(i * (dot_width + gap_width), width - 1);
const rect_width = @min(width - x, dot_width);
canvas.rect(.{
.x = @floatFromInt(x),
.x = @intCast(x),
.y = 0,
.width = @floatFromInt(rect_width),
.height = @floatFromInt(thickness),
.width = rect_width,
.height = thickness,
}, .on);
}
@ -146,10 +146,10 @@ fn drawDashed(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset {
const x = @min(i * dash_width, width - 1);
const rect_width = @min(width - x, dash_width);
canvas.rect(.{
.x = @floatFromInt(x),
.x = @intCast(x),
.y = 0,
.width = @floatFromInt(rect_width),
.height = @floatFromInt(thickness),
.width = rect_width,
.height = thickness,
}, .on);
}