Толщина линии

This commit is contained in:
2026-02-26 00:23:10 +03:00
parent 563c2a7535
commit f8731bde87
2 changed files with 71 additions and 10 deletions

View File

@@ -50,7 +50,7 @@ fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size
const stroke = randRgba(rng); const stroke = randRgba(rng);
try obj.setProperty(allocator, .{ .data = .{ .stroke_rgba = stroke } }); try obj.setProperty(allocator, .{ .data = .{ .stroke_rgba = stroke } });
obj.setProperty(allocator, .{ .data = .{ .fill_rgba = randRgba(rng) } }) catch {}; 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 } }); try obj.setProperty(allocator, .{ .data = .{ .thickness = thickness } });
switch (obj.shape) { switch (obj.shape) {

View File

@@ -1,3 +1,4 @@
const std = @import("std");
const Document = @import("../../models/Document.zig"); const Document = @import("../../models/Document.zig");
const pipeline = @import("pipeline.zig"); const pipeline = @import("pipeline.zig");
const DrawContext = pipeline.DrawContext; 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 w1 = ctx.localToWorld(x1, y1);
const b0 = ctx.worldToBuffer(w0.x, w0.y); const b0 = ctx.worldToBuffer(w0.x, w0.y);
const b1 = ctx.worldToBuffer(w1.x, w1.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 bw = ctx.buf_width;
const bh = ctx.buf_height; const bh = ctx.buf_height;
const dx: i32 = @intCast(@abs(bx1 - bx0)); 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 sx: i32 = if (bx0 < bx1) 1 else -1;
const sy: i32 = if (by0 < by1) 1 else -1; const sy: i32 = if (by0 < by1) 1 else -1;
var err = dx + dy; var err = dx - dy;
var x = bx0; var x = bx0;
var y = by0; 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) { while (true) {
if (x >= 0 and x < bw and y >= 0 and y < bh) { const near_start = @abs(x - bx0) + @abs(y - by0) <= thickness_i;
ctx.blendPixelAtBuffer(@intCast(x), @intCast(y), color); 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; if (x == bx1 and y == by1) break;
const e2 = 2 * err; const e2 = 2 * err;
if (e2 >= dy) { if (e2 >= -dy) {
err += dy; err -= dy;
x += sx; x += sx;
} }
if (e2 <= dx) { if (e2 <= dx) {
@@ -54,4 +109,10 @@ fn drawLineInBuffer(ctx: *DrawContext, bx0: i32, by0: i32, bx1: i32, by1: i32, c
y += sy; y += sy;
} }
} }
// Скруглённые концы: круги в крайних точках.
if (half > 0) {
fillCircleAtBuffer(ctx, bx0, by0, half, color);
fillCircleAtBuffer(ctx, bx1, by1, half, color);
}
} }