deps: update z2d to v0.4.0 (#3075)

Introduces static path methods and a reworked context API that makes
things generally cleaner.

This update incidentally fixed a bug we had before where the corner
triangle shade characters were drawn solid rather than medium shade.

### `Box_test_diff.ppm`:

![image](https://github.com/user-attachments/assets/64bf0a82-21a0-4fae-a423-bde6c5851648)
This commit is contained in:
Mitchell Hashimoto
2024-12-22 19:07:28 -08:00
committed by GitHub
8 changed files with 177 additions and 221 deletions

View File

@ -61,8 +61,8 @@
.hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8", .hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8",
}, },
.z2d = .{ .z2d = .{
.url = "git+https://github.com/vancluever/z2d?ref=main#285a796eb9c25a2389f087d008f0e60faf0b8eda", .url = "git+https://github.com/vancluever/z2d?ref=v0.4.0#4638bb02a9dc41cc2fb811f092811f6a951c752a",
.hash = "12206445aa45bcf0170ace371905f705aec1d8d4f61e7dd77839c6621b8c407680a5", .hash = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a",
}, },
}, },
} }

View File

@ -1,3 +1,3 @@
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for # This file is auto-generated! check build-support/check-zig-cache-hash.sh for
# more details. # more details.
"sha256-vP8f8KQyM4CwKlw7Esmxv1q4ANu8pDXXsnVorgpWCr4=" "sha256-lS5v5VdFCLnIyCq9mp7fd2pXhQmkkDFHHVdg4pf37PA="

View File

@ -220,7 +220,7 @@ pub fn renderGlyph(
metrics.cell_width, metrics.cell_width,
metrics.cell_height, metrics.cell_height,
); );
defer canvas.deinit(alloc); defer canvas.deinit();
// Perform the actual drawing // Perform the actual drawing
try self.draw(alloc, &canvas, cp); try self.draw(alloc, &canvas, cp);
@ -2233,18 +2233,10 @@ fn draw_branch_node(
@min(float_width - cx, float_height - cy), @min(float_width - cx, float_height - cy),
); );
var ctx: z2d.Context = .{ var ctx = canvas.getContext() catch return;
.surface = canvas.sfc, defer ctx.deinit();
.pattern = .{ ctx.setSource(.{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } });
.opaque_pattern = .{ ctx.setLineWidth(float_thick);
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
},
},
.line_width = float_thick,
};
var path = z2d.Path.init(canvas.alloc);
defer path.deinit();
// These @intFromFloat casts shouldn't ever fail since r can never // These @intFromFloat casts shouldn't ever fail since r can never
// be greater than cx or cy, so when subtracting it from them the // be greater than cx or cy, so when subtracting it from them the
@ -2259,13 +2251,13 @@ fn draw_branch_node(
self.rect(canvas, 0, h_top, @intFromFloat(@ceil(cx - r)), h_bottom); self.rect(canvas, 0, h_top, @intFromFloat(@ceil(cx - r)), h_bottom);
if (node.filled) { if (node.filled) {
path.arc(cx, cy, r, 0, std.math.pi * 2, false, null) catch return; ctx.arc(cx, cy, r, 0, std.math.pi * 2) catch return;
path.close() catch return; ctx.closePath() catch return;
ctx.fill(canvas.alloc, path) catch return; ctx.fill() catch return;
} else { } else {
path.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2, false, null) catch return; ctx.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2) catch return;
path.close() catch return; ctx.closePath() catch return;
ctx.stroke(canvas.alloc, path) catch return; ctx.stroke() catch return;
} }
} }
@ -2290,27 +2282,21 @@ fn draw_circle(
}; };
const r: f64 = 0.5 * @min(float_width, float_height); const r: f64 = 0.5 * @min(float_width, float_height);
var ctx: z2d.Context = .{ var ctx = canvas.getContext() catch return;
.surface = canvas.sfc, defer ctx.deinit();
.pattern = .{ ctx.setSource(.{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } });
.opaque_pattern = .{ ctx.setLineWidth(
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, @floatFromInt(Thickness.light.height(self.metrics.box_thickness)),
}, );
},
.line_width = @floatFromInt(Thickness.light.height(self.metrics.box_thickness)),
};
var path = z2d.Path.init(canvas.alloc);
defer path.deinit();
if (filled) { if (filled) {
path.arc(x, y, r, 0, std.math.pi * 2, false, null) catch return; ctx.arc(x, y, r, 0, std.math.pi * 2) catch return;
path.close() catch return; ctx.closePath() catch return;
ctx.fill(canvas.alloc, path) catch return; ctx.fill() catch return;
} else { } else {
path.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2, false, null) catch return; ctx.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2) catch return;
path.close() catch return; ctx.closePath() catch return;
ctx.stroke(canvas.alloc, path) catch return; ctx.stroke() catch return;
} }
} }
@ -2528,31 +2514,30 @@ fn draw_smooth_mosaic(
const center: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2); const center: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2);
const right: f64 = @floatFromInt(self.metrics.cell_width); const right: f64 = @floatFromInt(self.metrics.cell_width);
var path = z2d.Path.init(canvas.alloc); var path: z2d.StaticPath(12) = .{};
defer path.deinit(); path.init();
if (mosaic.tl) try path.lineTo(left, top); if (mosaic.tl) path.lineTo(left, top);
if (mosaic.ul) try path.lineTo(left, upper); if (mosaic.ul) path.lineTo(left, upper);
if (mosaic.ll) try path.lineTo(left, lower); if (mosaic.ll) path.lineTo(left, lower);
if (mosaic.bl) try path.lineTo(left, bottom); if (mosaic.bl) path.lineTo(left, bottom);
if (mosaic.bc) try path.lineTo(center, bottom); if (mosaic.bc) path.lineTo(center, bottom);
if (mosaic.br) try path.lineTo(right, bottom); if (mosaic.br) path.lineTo(right, bottom);
if (mosaic.lr) try path.lineTo(right, lower); if (mosaic.lr) path.lineTo(right, lower);
if (mosaic.ur) try path.lineTo(right, upper); if (mosaic.ur) path.lineTo(right, upper);
if (mosaic.tr) try path.lineTo(right, top); if (mosaic.tr) path.lineTo(right, top);
if (mosaic.tc) try path.lineTo(center, top); if (mosaic.tc) path.lineTo(center, top);
try path.close(); path.close();
var ctx: z2d.Context = .{ try z2d.painter.fill(
.surface = canvas.sfc, canvas.alloc,
.pattern = .{ &canvas.sfc,
.opaque_pattern = .{ &.{ .opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
}, } },
}, &path.nodes,
}; .{},
);
try ctx.fill(canvas.alloc, path);
} }
fn draw_edge_triangle( fn draw_edge_triangle(
@ -2567,9 +2552,6 @@ fn draw_edge_triangle(
const center: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2); const center: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2);
const right: f64 = @floatFromInt(self.metrics.cell_width); const right: f64 = @floatFromInt(self.metrics.cell_width);
var path = z2d.Path.init(canvas.alloc);
defer path.deinit();
const x0, const y0, const x1, const y1 = switch (edge) { const x0, const y0, const x1, const y1 = switch (edge) {
.top => .{ right, upper, left, upper }, .top => .{ right, upper, left, upper },
.left => .{ left, upper, left, lower }, .left => .{ left, upper, left, lower },
@ -2577,21 +2559,23 @@ fn draw_edge_triangle(
.right => .{ right, lower, right, upper }, .right => .{ right, lower, right, upper },
}; };
try path.moveTo(center, middle); var path: z2d.StaticPath(5) = .{};
try path.lineTo(x0, y0); path.init();
try path.lineTo(x1, y1);
try path.close();
var ctx: z2d.Context = .{ path.moveTo(center, middle);
.surface = canvas.sfc, path.lineTo(x0, y0);
.pattern = .{ path.lineTo(x1, y1);
.opaque_pattern = .{ path.close();
try z2d.painter.fill(
canvas.alloc,
&canvas.sfc,
&.{ .opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
}, } },
}, &path.nodes,
}; .{},
);
try ctx.fill(canvas.alloc, path);
} }
fn draw_arc( fn draw_arc(
@ -2612,25 +2596,17 @@ fn draw_arc(
// Fraction away from the center to place the middle control points, // Fraction away from the center to place the middle control points,
const s: f64 = 0.25; const s: f64 = 0.25;
var ctx: z2d.Context = .{ var ctx = try canvas.getContext();
.surface = canvas.sfc, defer ctx.deinit();
.pattern = .{ ctx.setSource(.{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } });
.opaque_pattern = .{ ctx.setLineWidth(float_thick);
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, ctx.setLineCapMode(.round);
},
},
.line_width = float_thick,
.line_cap_mode = .round,
};
var path = z2d.Path.init(canvas.alloc);
defer path.deinit();
switch (corner) { switch (corner) {
.tl => { .tl => {
try path.moveTo(center_x, 0); try ctx.moveTo(center_x, 0);
try path.lineTo(center_x, center_y - r); try ctx.lineTo(center_x, center_y - r);
try path.curveTo( try ctx.curveTo(
center_x, center_x,
center_y - s * r, center_y - s * r,
center_x - s * r, center_x - s * r,
@ -2638,12 +2614,12 @@ fn draw_arc(
center_x - r, center_x - r,
center_y, center_y,
); );
try path.lineTo(0, center_y); try ctx.lineTo(0, center_y);
}, },
.tr => { .tr => {
try path.moveTo(center_x, 0); try ctx.moveTo(center_x, 0);
try path.lineTo(center_x, center_y - r); try ctx.lineTo(center_x, center_y - r);
try path.curveTo( try ctx.curveTo(
center_x, center_x,
center_y - s * r, center_y - s * r,
center_x + s * r, center_x + s * r,
@ -2651,12 +2627,12 @@ fn draw_arc(
center_x + r, center_x + r,
center_y, center_y,
); );
try path.lineTo(float_width, center_y); try ctx.lineTo(float_width, center_y);
}, },
.bl => { .bl => {
try path.moveTo(center_x, float_height); try ctx.moveTo(center_x, float_height);
try path.lineTo(center_x, center_y + r); try ctx.lineTo(center_x, center_y + r);
try path.curveTo( try ctx.curveTo(
center_x, center_x,
center_y + s * r, center_y + s * r,
center_x - s * r, center_x - s * r,
@ -2664,12 +2640,12 @@ fn draw_arc(
center_x - r, center_x - r,
center_y, center_y,
); );
try path.lineTo(0, center_y); try ctx.lineTo(0, center_y);
}, },
.br => { .br => {
try path.moveTo(center_x, float_height); try ctx.moveTo(center_x, float_height);
try path.lineTo(center_x, center_y + r); try ctx.lineTo(center_x, center_y + r);
try path.curveTo( try ctx.curveTo(
center_x, center_x,
center_y + s * r, center_y + s * r,
center_x + s * r, center_x + s * r,
@ -2677,10 +2653,10 @@ fn draw_arc(
center_x + r, center_x + r,
center_y, center_y,
); );
try path.lineTo(float_width, center_y); try ctx.lineTo(float_width, center_y);
}, },
} }
try ctx.stroke(canvas.alloc, path); try ctx.stroke();
} }
fn draw_dash_horizontal( fn draw_dash_horizontal(
@ -2912,14 +2888,9 @@ fn draw_separated_block_quadrant(self: Box, canvas: *font.sprite.Canvas, comptim
} }
} }
var ctx: z2d.Context = .{ var ctx = try canvas.getContext();
.surface = canvas.sfc, defer ctx.deinit();
.pattern = .{ ctx.setSource(.{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } });
.opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } },
},
},
};
const gap: f64 = @max(1.0, @as(f64, @floatFromInt(self.metrics.cell_width)) * 0.10) / 2.0; const gap: f64 = @max(1.0, @as(f64, @floatFromInt(self.metrics.cell_width)) * 0.10) / 2.0;
const left: f64 = gap; const left: f64 = gap;
@ -2953,15 +2924,14 @@ fn draw_separated_block_quadrant(self: Box, canvas: *font.sprite.Canvas, comptim
}, },
else => unreachable, else => unreachable,
}; };
var path = z2d.Path.init(canvas.alloc); try ctx.moveTo(x1, y1);
defer path.deinit(); try ctx.lineTo(x2, y1);
try path.moveTo(x1, y1); try ctx.lineTo(x2, y2);
try path.lineTo(x2, y1); try ctx.lineTo(x1, y2);
try path.lineTo(x2, y2); try ctx.closePath();
try path.lineTo(x1, y2);
try path.close();
try ctx.fill(canvas.alloc, path);
} }
try ctx.fill();
} }
test "all" { test "all" {

View File

@ -58,7 +58,7 @@ pub fn renderGlyph(
) !font.Glyph { ) !font.Glyph {
// Create the canvas we'll use to draw // Create the canvas we'll use to draw
var canvas = try font.sprite.Canvas.init(alloc, self.width, self.height); var canvas = try font.sprite.Canvas.init(alloc, self.width, self.height);
defer canvas.deinit(alloc); defer canvas.deinit();
// Perform the actual drawing // Perform the actual drawing
try self.draw(alloc, &canvas, cp); try self.draw(alloc, &canvas, cp);

View File

@ -74,6 +74,9 @@ pub const Color = enum(u8) {
_, _,
}; };
/// This is a managed struct, it keeps a reference to the allocator that is
/// used to initialize it, and the same allocator is used for any further
/// necessary allocations when drawing.
pub const Canvas = struct { pub const Canvas = struct {
/// The underlying z2d surface. /// The underlying z2d surface.
sfc: z2d.Surface, sfc: z2d.Surface,
@ -88,16 +91,13 @@ pub const Canvas = struct {
@intCast(width), @intCast(width),
@intCast(height), @intCast(height),
); );
errdefer sfc.deinit(alloc);
return .{ return .{ .sfc = sfc, .alloc = alloc };
.sfc = sfc,
.alloc = alloc,
};
} }
pub fn deinit(self: *Canvas, alloc: Allocator) void { pub fn deinit(self: *Canvas) void {
_ = alloc; self.sfc.deinit(self.alloc);
self.sfc.deinit();
self.* = undefined; self.* = undefined;
} }
@ -148,27 +148,18 @@ pub const Canvas = struct {
return region; return region;
} }
/// Acquires a z2d drawing context, caller MUST deinit context.
pub fn getContext(self: *Canvas) Allocator.Error!z2d.Context {
return try z2d.Context.init(self.alloc, &self.sfc);
}
/// Draw and fill a single pixel /// Draw and fill a single pixel
pub fn pixel(self: *Canvas, x: u32, y: u32, color: Color) void { pub fn pixel(self: *Canvas, x: u32, y: u32, color: Color) void {
self.sfc.putPixel( self.sfc.putPixel(
@intCast(x), @intCast(x),
@intCast(y), @intCast(y),
.{ .alpha8 = .{ .a = @intFromEnum(color) } }, .{ .alpha8 = .{ .a = @intFromEnum(color) } },
) 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.
},
error.InvalidHeight,
error.InvalidWidth,
error.InvalidPixelFormat,
=> {
std.log.err("unexpected (considered impossible) error err={}", .{e});
unreachable; // This shouldn't be possible.
},
};
} }
/// Draw and fill a rectangle. This is the main primitive for drawing /// Draw and fill a rectangle. This is the main primitive for drawing
@ -192,94 +183,89 @@ pub const Canvas = struct {
/// Draw and fill a quad. /// Draw and fill a quad.
pub fn quad(self: *Canvas, q: Quad(f64), color: Color) !void { pub fn quad(self: *Canvas, q: Quad(f64), color: Color) !void {
var ctx: z2d.Context = .{ var path: z2d.StaticPath(6) = .{};
.surface = self.sfc, path.init();
.pattern = .{
.opaque_pattern = .{ path.moveTo(q.p0.x, q.p0.y);
path.lineTo(q.p1.x, q.p1.y);
path.lineTo(q.p2.x, q.p2.y);
path.lineTo(q.p3.x, q.p3.y);
path.close();
try z2d.painter.fill(
self.alloc,
&self.sfc,
&.{ .opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
}, } },
}, &path.nodes,
}; .{},
);
var path = z2d.Path.init(self.alloc);
defer path.deinit();
try path.moveTo(q.p0.x, q.p0.y);
try path.lineTo(q.p1.x, q.p1.y);
try path.lineTo(q.p2.x, q.p2.y);
try path.lineTo(q.p3.x, q.p3.y);
try path.close();
try ctx.fill(self.alloc, path);
} }
/// Draw and fill a triangle. /// Draw and fill a triangle.
pub fn triangle(self: *Canvas, t: Triangle(f64), color: Color) !void { pub fn triangle(self: *Canvas, t: Triangle(f64), color: Color) !void {
var ctx: z2d.Context = .{ var path: z2d.StaticPath(5) = .{};
.surface = self.sfc, path.init();
.pattern = .{
.opaque_pattern = .{ path.moveTo(t.p0.x, t.p0.y);
path.lineTo(t.p1.x, t.p1.y);
path.lineTo(t.p2.x, t.p2.y);
path.close();
try z2d.painter.fill(
self.alloc,
&self.sfc,
&.{ .opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
}, } },
}, &path.nodes,
}; .{},
);
var path = z2d.Path.init(self.alloc);
defer path.deinit();
try path.moveTo(t.p0.x, t.p0.y);
try path.lineTo(t.p1.x, t.p1.y);
try path.lineTo(t.p2.x, t.p2.y);
try path.close();
try ctx.fill(self.alloc, path);
} }
pub fn triangle_outline(self: *Canvas, t: Triangle(f64), thickness: f64, color: Color) !void { pub fn triangle_outline(self: *Canvas, t: Triangle(f64), thickness: f64, color: Color) !void {
var ctx: z2d.Context = .{ var path: z2d.StaticPath(5) = .{};
.surface = self.sfc, path.init();
.pattern = .{
.opaque_pattern = .{ path.moveTo(t.p0.x, t.p0.y);
path.lineTo(t.p1.x, t.p1.y);
path.lineTo(t.p2.x, t.p2.y);
try z2d.painter.stroke(
self.alloc,
&self.sfc,
&.{ .opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
}, } },
}, &path.nodes,
.line_width = thickness, .{
.line_cap_mode = .round, .line_cap_mode = .round,
}; .line_width = thickness,
},
var path = z2d.Path.init(self.alloc); );
defer path.deinit();
try path.moveTo(t.p0.x, t.p0.y);
try path.lineTo(t.p1.x, t.p1.y);
try path.lineTo(t.p2.x, t.p2.y);
// try path.close();
try ctx.stroke(self.alloc, path);
// try ctx.fill(self.alloc, path);
} }
/// Stroke a line. /// Stroke a line.
pub fn line(self: *Canvas, l: Line(f64), thickness: f64, color: Color) !void { pub fn line(self: *Canvas, l: Line(f64), thickness: f64, color: Color) !void {
var ctx: z2d.Context = .{ var path: z2d.StaticPath(3) = .{};
.surface = self.sfc, path.init();
.pattern = .{
.opaque_pattern = .{ path.moveTo(l.p0.x, l.p0.y);
path.lineTo(l.p1.x, l.p1.y);
try z2d.painter.stroke(
self.alloc,
&self.sfc,
&.{ .opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } },
}, } },
}, &path.nodes,
.line_width = thickness, .{
.line_cap_mode = .round, .line_cap_mode = .round,
}; .line_width = thickness,
},
var path = z2d.Path.init(self.alloc); );
defer path.deinit();
try path.moveTo(l.p0.x, l.p0.y);
try path.lineTo(l.p1.x, l.p1.y);
try ctx.stroke(self.alloc, path);
} }
pub fn invert(self: *Canvas) void { pub fn invert(self: *Canvas) void {

View File

@ -17,7 +17,7 @@ pub fn renderGlyph(
) !font.Glyph { ) !font.Glyph {
// Make a canvas of the desired size // Make a canvas of the desired size
var canvas = try font.sprite.Canvas.init(alloc, width, height); var canvas = try font.sprite.Canvas.init(alloc, width, height);
defer canvas.deinit(alloc); defer canvas.deinit();
// Draw the appropriate sprite // Draw the appropriate sprite
switch (sprite) { switch (sprite) {

Binary file not shown.

View File

@ -38,7 +38,7 @@ pub fn renderGlyph(
.strikethrough => try drawSingle(alloc, width, line_thickness), .strikethrough => try drawSingle(alloc, width, line_thickness),
else => unreachable, else => unreachable,
}; };
defer canvas.deinit(alloc); defer canvas.deinit();
// Write the drawing to the atlas // Write the drawing to the atlas
const region = try canvas.writeAtlas(alloc, atlas); const region = try canvas.writeAtlas(alloc, atlas);