From f8731bde87639fac95b42253aeab785e63c69bd0 Mon Sep 17 00:00:00 2001 From: Roman Pytkov Date: Thu, 26 Feb 2026 00:23:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A2=D0=BE=D0=BB=D1=89=D0=B8=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BB=D0=B8=D0=BD=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/random_document.zig | 2 +- src/render/cpu/line.zig | 79 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/random_document.zig b/src/random_document.zig index 9320108..727ece3 100644 --- a/src/random_document.zig +++ b/src/random_document.zig @@ -50,7 +50,7 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size const stroke = randRgba(rng); try obj.setProperty(allocator, .{ .data = .{ .stroke_rgba = stroke } }); obj.setProperty(allocator, .{ .data = .{ .fill_rgba = randRgba(rng) } }) catch {}; - const thickness = randFloat(rng, max_x * 0.01, max_x * 0.1); + const thickness = randFloat(rng, max_x * 0.001, max_x * 0.01); try obj.setProperty(allocator, .{ .data = .{ .thickness = thickness } }); switch (obj.shape) { diff --git a/src/render/cpu/line.zig b/src/render/cpu/line.zig index e1a6cf7..1315a71 100644 --- a/src/render/cpu/line.zig +++ b/src/render/cpu/line.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const Document = @import("../../models/Document.zig"); const pipeline = @import("pipeline.zig"); const DrawContext = pipeline.DrawContext; @@ -23,30 +24,84 @@ pub fn drawLine(ctx: *DrawContext, x0: f32, y0: f32, x1: f32, y1: f32, color: Co const w1 = ctx.localToWorld(x1, y1); const b0 = ctx.worldToBuffer(w0.x, w0.y); const b1 = ctx.worldToBuffer(w1.x, w1.y); - drawLineInBuffer(ctx, b0.x, b0.y, b1.x, b1.y, color, thickness); + const t = &ctx.transform; + const scale = @sqrt(t.scale.scale_x * ctx.scale_x * t.scale.scale_y * ctx.scale_y); + const thickness_px: u32 = @max(@as(u32, 1), @as(u32, @intFromFloat(std.math.round(thickness * scale)))); + drawLineInBuffer(ctx, b0.x, b0.y, b1.x, b1.y, color, thickness_px); } -fn drawLineInBuffer(ctx: *DrawContext, bx0: i32, by0: i32, bx1: i32, by1: i32, color: Color.PMA, thickness: f32) void { +/// Точка (px, py) лежит внутри/на круге с центром (cx, cy) и радиусом в квадрате r_sq. +fn insideCircle(px: i32, py: i32, cx: i32, cy: i32, r_sq: i32) bool { + const dx = px - cx; + const dy = py - cy; + return dx * dx + dy * dy <= r_sq; +} + +/// Заливает круг в буфере (для скруглённых концов отрезка). +fn fillCircleAtBuffer(ctx: *DrawContext, cx: i32, cy: i32, radius: i32, color: Color.PMA) void { + const bw: i32 = @intCast(ctx.buf_width); + const bh: i32 = @intCast(ctx.buf_height); + const r_sq = radius * radius; + var dy: i32 = -radius; + while (dy <= radius) : (dy += 1) { + var dx: i32 = -radius; + while (dx <= radius) : (dx += 1) { + const px = cx + dx; + const py = cy + dy; + if (!insideCircle(px, py, cx, cy, r_sq)) continue; + if (px >= 0 and px < bw and py >= 0 and py < bh) { + ctx.blendPixelAtBuffer(@intCast(px), @intCast(py), color); + } + } + } +} + +fn drawLineInBuffer(ctx: *DrawContext, bx0: i32, by0: i32, bx1: i32, by1: i32, color: Color.PMA, thickness_px: u32) void { const bw = ctx.buf_width; const bh = ctx.buf_height; const dx: i32 = @intCast(@abs(bx1 - bx0)); - const dy: i32 = -@as(i32, @intCast(@abs(by1 - by0))); + const dy: i32 = @intCast(@abs(by1 - by0)); const sx: i32 = if (bx0 < bx1) 1 else -1; const sy: i32 = if (by0 < by1) 1 else -1; - var err = dx + dy; + var err = dx - dy; var x = bx0; var y = by0; + const half: i32 = @intCast(thickness_px / 2); + const half_sq = half * half; + const thickness_i: i32 = @intCast(thickness_px); - _ = thickness; + // Брезенхем + штамп по доминирующей оси: горизонтальная линия — вертикальный штамп, вертикальная — горизонтальный. + const more_horizontal = dx >= dy; while (true) { - if (x >= 0 and x < bw and y >= 0 and y < bh) { - ctx.blendPixelAtBuffer(@intCast(x), @intCast(y), color); + const near_start = @abs(x - bx0) + @abs(y - by0) <= thickness_i; + const near_end = @abs(x - bx1) + @abs(y - by1) <= thickness_i; + + if (more_horizontal) { + var yo: i32 = -half; + while (yo <= half) : (yo += 1) { + const py = y + yo; + if (near_start and insideCircle(x, py, bx0, by0, half_sq)) continue; + if (near_end and insideCircle(x, py, bx1, by1, half_sq)) continue; + if (x >= 0 and x < @as(i32, @intCast(bw)) and py >= 0 and py < @as(i32, @intCast(bh))) { + ctx.blendPixelAtBuffer(@intCast(x), @intCast(py), color); + } + } + } else { + var xo: i32 = -half; + while (xo <= half) : (xo += 1) { + const px = x + xo; + if (near_start and insideCircle(px, y, bx0, by0, half_sq)) continue; + if (near_end and insideCircle(px, y, bx1, by1, half_sq)) continue; + if (px >= 0 and px < @as(i32, @intCast(bw)) and y >= 0 and y < @as(i32, @intCast(bh))) { + ctx.blendPixelAtBuffer(@intCast(px), @intCast(y), color); + } + } } if (x == bx1 and y == by1) break; const e2 = 2 * err; - if (e2 >= dy) { - err += dy; + if (e2 >= -dy) { + err -= dy; x += sx; } if (e2 <= dx) { @@ -54,4 +109,10 @@ fn drawLineInBuffer(ctx: *DrawContext, bx0: i32, by0: i32, bx1: i32, by1: i32, c y += sy; } } + + // Скруглённые концы: круги в крайних точках. + if (half > 0) { + fillCircleAtBuffer(ctx, bx0, by0, half, color); + fillCircleAtBuffer(ctx, bx1, by1, half, color); + } }