Толщина линии
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user