font/sprite: rework yQuads and friends for better alignment with draw_block

This improves "outer edge" alignment of octants and other elements drawn
using `yQuads` and friends with blocks drawn with `draw_block` -- this
should guarantee alignment along a continuous edge, but may result in a
1px overlap of opposing edges (such as a top half block followed by a
bottom half block with an odd cell height, they will both have the
center row filled).

This is very necessary since several block elements are needed to
complete the set of octants, since dedicated octant characters aren't
included when they would be redundant.
This commit is contained in:
Qwerasd
2025-05-30 17:52:31 -06:00
parent 9b45638c15
commit dd670f5107
2 changed files with 55 additions and 35 deletions

View File

@@ -2488,10 +2488,10 @@ fn draw_sextant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void {
if (sex.tl) self.rect(canvas, 0, 0, x_halfs[0], y_thirds[0]);
if (sex.tr) self.rect(canvas, x_halfs[1], 0, self.metrics.cell_width, y_thirds[0]);
if (sex.ml) self.rect(canvas, 0, y_thirds[0], x_halfs[0], y_thirds[1]);
if (sex.mr) self.rect(canvas, x_halfs[1], y_thirds[0], self.metrics.cell_width, y_thirds[1]);
if (sex.bl) self.rect(canvas, 0, y_thirds[1], x_halfs[0], self.metrics.cell_height);
if (sex.br) self.rect(canvas, x_halfs[1], y_thirds[1], self.metrics.cell_width, self.metrics.cell_height);
if (sex.ml) self.rect(canvas, 0, y_thirds[1], x_halfs[0], y_thirds[2]);
if (sex.mr) self.rect(canvas, x_halfs[1], y_thirds[1], self.metrics.cell_width, y_thirds[2]);
if (sex.bl) self.rect(canvas, 0, y_thirds[3], x_halfs[0], self.metrics.cell_height);
if (sex.br) self.rect(canvas, x_halfs[1], y_thirds[3], self.metrics.cell_width, self.metrics.cell_height);
}
fn draw_octant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void {
@@ -2545,42 +2545,58 @@ fn draw_octant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void {
const oct = octants[cp - octant_min];
if (oct.@"1") self.rect(canvas, 0, 0, x_halfs[0], y_quads[0]);
if (oct.@"2") self.rect(canvas, x_halfs[1], 0, self.metrics.cell_width, y_quads[0]);
if (oct.@"3") self.rect(canvas, 0, y_quads[0], x_halfs[0], y_quads[1]);
if (oct.@"4") self.rect(canvas, x_halfs[1], y_quads[0], self.metrics.cell_width, y_quads[1]);
if (oct.@"5") self.rect(canvas, 0, y_quads[1], x_halfs[0], y_quads[2]);
if (oct.@"6") self.rect(canvas, x_halfs[1], y_quads[1], self.metrics.cell_width, y_quads[2]);
if (oct.@"7") self.rect(canvas, 0, y_quads[2], x_halfs[0], self.metrics.cell_height);
if (oct.@"8") self.rect(canvas, x_halfs[1], y_quads[2], self.metrics.cell_width, self.metrics.cell_height);
if (oct.@"3") self.rect(canvas, 0, y_quads[1], x_halfs[0], y_quads[2]);
if (oct.@"4") self.rect(canvas, x_halfs[1], y_quads[1], self.metrics.cell_width, y_quads[2]);
if (oct.@"5") self.rect(canvas, 0, y_quads[3], x_halfs[0], y_quads[4]);
if (oct.@"6") self.rect(canvas, x_halfs[1], y_quads[3], self.metrics.cell_width, y_quads[4]);
if (oct.@"7") self.rect(canvas, 0, y_quads[5], x_halfs[0], self.metrics.cell_height);
if (oct.@"8") self.rect(canvas, x_halfs[1], y_quads[5], self.metrics.cell_width, self.metrics.cell_height);
}
/// xHalfs[0] should be used as the right edge of a left-aligned half.
/// xHalfs[1] should be used as the left edge of a right-aligned half.
fn xHalfs(self: Box) [2]u32 {
const float_width: f64 = @floatFromInt(self.metrics.cell_width);
const half_width: u32 = @intFromFloat(@round(0.5 * float_width));
return .{ half_width, self.metrics.cell_width - half_width };
}
/// Use these values as such:
/// yThirds[0] bottom edge of the first third.
/// yThirds[1] top edge of the second third.
/// yThirds[2] bottom edge of the second third.
/// yThirds[3] top edge of the final third.
fn yThirds(self: Box) [4]u32 {
const float_height: f64 = @floatFromInt(self.metrics.cell_height);
const one_third_height: u32 = @intFromFloat(@round(one_third * float_height));
const two_thirds_height: u32 = @intFromFloat(@round(two_thirds * float_height));
return .{
@as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2))),
@as(u32, @intFromFloat(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2)),
one_third_height,
self.metrics.cell_height - two_thirds_height,
two_thirds_height,
self.metrics.cell_height - one_third_height,
};
}
fn yThirds(self: Box) [2]u32 {
return switch (@mod(self.metrics.cell_height, 3)) {
0 => .{ self.metrics.cell_height / 3, 2 * self.metrics.cell_height / 3 },
1 => .{ self.metrics.cell_height / 3, 2 * self.metrics.cell_height / 3 + 1 },
2 => .{ self.metrics.cell_height / 3 + 1, 2 * self.metrics.cell_height / 3 },
else => unreachable,
};
}
// assume octants might be striped across multiple rows of cells. to maximize
// distance between excess pixellines, we want (1) an arbitrary region (there
// will be a pattern of 1'-3-1'-3-1'-3 no matter what), (2) discontiguous
// regions (0 and 2 or 1 and 3), and (3) an arbitrary three regions (there will
// be a pattern of 3-1-3-1-3-1 no matter what).
fn yQuads(self: Box) [3]u32 {
return switch (@mod(self.metrics.cell_height, 4)) {
0 => .{ self.metrics.cell_height / 4, 2 * self.metrics.cell_height / 4, 3 * self.metrics.cell_height / 4 },
1 => .{ self.metrics.cell_height / 4, 2 * self.metrics.cell_height / 4 + 1, 3 * self.metrics.cell_height / 4 },
2 => .{ self.metrics.cell_height / 4 + 1, 2 * self.metrics.cell_height / 4, 3 * self.metrics.cell_height / 4 + 1 },
3 => .{ self.metrics.cell_height / 4 + 1, 2 * self.metrics.cell_height / 4 + 1, 3 * self.metrics.cell_height / 4 },
else => unreachable,
/// Use these values as such:
/// yQuads[0] bottom edge of first quarter.
/// yQuads[1] top edge of second quarter.
/// yQuads[2] bottom edge of second quarter.
/// yQuads[3] top edge of third quarter.
/// yQuads[4] bottom edge of third quarter
/// yQuads[5] top edge of fourth quarter.
fn yQuads(self: Box) [6]u32 {
const float_height: f64 = @floatFromInt(self.metrics.cell_height);
const quarter_height: u32 = @intFromFloat(@round(0.25 * float_height));
const half_height: u32 = @intFromFloat(@round(0.50 * float_height));
const three_quarters_height: u32 = @intFromFloat(@round(0.75 * float_height));
return .{
quarter_height,
self.metrics.cell_height - three_quarters_height,
half_height,
self.metrics.cell_height - half_height,
three_quarters_height,
self.metrics.cell_height - quarter_height,
};
}
@@ -2591,8 +2607,12 @@ fn draw_smooth_mosaic(
) !void {
const y_thirds = self.yThirds();
const top: f64 = 0.0;
const upper: f64 = @floatFromInt(y_thirds[0]);
const lower: f64 = @floatFromInt(y_thirds[1]);
// We average the edge positions for the y_thirds boundaries here
// rather than having to deal with varying alignments depending on
// the surrounding pieces. The most this will be off by is half of
// a pixel, so hopefully it's not noticeable.
const upper: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[0])) + @as(f64, @floatFromInt(y_thirds[1])));
const lower: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[2])) + @as(f64, @floatFromInt(y_thirds[3])));
const bottom: f64 = @floatFromInt(self.metrics.cell_height);
const left: f64 = 0.0;
const center: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2);

Binary file not shown.