Дополнительный буфер для отрисовки broken line

This commit is contained in:
2026-02-26 13:48:42 +03:00
parent 0eee436150
commit 05f5481a42
4 changed files with 59 additions and 11 deletions

View File

@@ -182,7 +182,7 @@ pub fn renderDocument(self: *CpuRenderEngine, document: *const Document, canvas_
defer self._allocator.free(pixels);
for (pixels) |*p| p.* = .{ .r = 255, .g = 255, .b = 255, .a = 255 };
cpu_draw.drawDocument(pixels, width, height, visible_rect, document, canvas_size);
try cpu_draw.drawDocument(pixels, width, height, visible_rect, document, canvas_size, self._allocator);
return try dvui.textureCreate(pixels, width, height, .nearest);
}

View File

@@ -9,15 +9,30 @@ const Object = Document.Object;
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
const default_thickness: f32 = 2.0;
/// Ломаная по точкам, обводка stroke_rgba.
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
/// Ломаная по точкам, обводка stroke_rgba
pub fn draw(
ctx: *DrawContext,
obj: *const Object,
allocator: std.mem.Allocator,
) !void {
const p_prop = obj.getProperty(.points) orelse return;
const pts = p_prop.points.items;
if (pts.len < 2) return;
const stroke = if (obj.getProperty(.stroke_rgba)) |s| pipeline.rgbaToPma(s.stroke_rgba) else default_stroke;
const thickness = if (obj.getProperty(.thickness)) |t| t.thickness else default_thickness;
const buffer = try allocator.alloc(Color.PMA, ctx.buf_width * ctx.buf_height);
@memset(buffer, .{ .r = 0, .g = 0, .b = 0, .a = 0 });
defer allocator.free(buffer);
var copy_ctx = ctx.*;
copy_ctx.pixels = buffer;
copy_ctx.replace_mode = true;
var i: usize = 0;
while (i + 1 < pts.len) : (i += 1) {
line.drawLine(ctx, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, stroke, thickness);
line.drawLine(&copy_ctx, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, stroke, thickness);
}
ctx.compositeDrawerContext(&copy_ctx, copy_ctx.transform.opacity);
}

View File

@@ -30,7 +30,12 @@ fn isVisible(obj: *const Object) bool {
return if (obj.getProperty(.visible)) |p| p.visible else true;
}
fn drawObject(ctx: *DrawContext, obj: *const Object, parent_transform: Transform) void {
fn drawObject(
ctx: *DrawContext,
obj: *const Object,
parent_transform: Transform,
allocator: std.mem.Allocator,
) !void {
if (!isVisible(obj)) return;
const local = getLocalTransform(obj);
const world = Transform.compose(parent_transform, local);
@@ -39,16 +44,17 @@ fn drawObject(ctx: *DrawContext, obj: *const Object, parent_transform: Transform
switch (obj.shape) {
.line => line.draw(ctx, obj),
.ellipse => ellipse.draw(ctx, obj),
.broken => broken.draw(ctx, obj),
.broken => try broken.draw(ctx, obj, allocator),
.arc => arc.draw(ctx, obj),
}
for (obj.children.items) |*child| {
drawObject(ctx, child, world);
try drawObject(ctx, child, world, allocator);
}
}
/// Рекурсивно рисует документ в буфер (объекты и потомки по порядку).
/// allocator опционален; если передан, ломаные рисуются через слой (без двойного наложения при alpha < 1).
pub fn drawDocument(
pixels: []@import("dvui").Color.PMA,
buf_width: u32,
@@ -56,7 +62,8 @@ pub fn drawDocument(
visible_rect: Rect_i,
document: *const Document,
canvas_size: Size_i,
) void {
allocator: std.mem.Allocator,
) !void {
const scale_x: f32 = if (document.size.w > 0) @as(f32, @floatFromInt(canvas_size.w)) / document.size.w else 0;
const scale_y: f32 = if (document.size.h > 0) @as(f32, @floatFromInt(canvas_size.h)) / document.size.h else 0;
@@ -70,6 +77,6 @@ pub fn drawDocument(
};
const identity = Transform{};
for (document.objects.items) |*obj| {
drawObject(&ctx, obj, identity);
try drawObject(&ctx, obj, identity, allocator);
}
}

View File

@@ -45,6 +45,8 @@ pub const DrawContext = struct {
scale_x: f32,
scale_y: f32,
transform: Transform = .{},
/// Если true, blendPixelAtBuffer перезаписывает пиксель без бленда
replace_mode: bool = false,
pub fn setTransform(self: *DrawContext, t: Transform) void {
self.transform = t;
@@ -111,12 +113,16 @@ pub const DrawContext = struct {
};
}
/// Смешивает цвет в пикселе буфера с учётом opacity трансформа.
/// Смешивает цвет в пикселе буфера с учётом opacity трансформа. В replace_mode просто перезаписывает пиксель.
pub fn blendPixelAtBuffer(self: *DrawContext, bx: u32, by: u32, color: Color.PMA) void {
if (bx >= self.buf_width or by >= self.buf_height) return;
const t = &self.transform;
const idx = by * self.buf_width + bx;
const dst = &self.pixels[idx];
if (self.replace_mode) {
dst.* = color;
return;
}
const t = &self.transform;
const a = @as(f32, @floatFromInt(color.a)) / 255.0 * t.opacity;
const src_r = @as(f32, @floatFromInt(color.r)) * t.opacity;
const src_g = @as(f32, @floatFromInt(color.g)) * t.opacity;
@@ -128,6 +134,26 @@ pub const DrawContext = struct {
dst.a = @intFromFloat(std.math.clamp(a * 255 + inv_a * @as(f32, @floatFromInt(dst.a)), 0, 255));
}
/// Накладывает буфер другого контекста на этот с заданной прозрачностью (один бленд на пиксель). Размеры буферов должны совпадать.
pub fn compositeDrawerContext(self: *DrawContext, other: *const DrawContext, opacity: f32) void {
if (self.buf_width != other.buf_width or self.buf_height != other.buf_height) return;
const n = self.buf_width * self.buf_height;
for (0..n) |i| {
const src = other.pixels[i];
if (src.a == 0) continue;
const dst = &self.pixels[i];
const a = @as(f32, @floatFromInt(src.a)) / 255.0 * opacity;
const src_r = @as(f32, @floatFromInt(src.r)) * opacity;
const src_g = @as(f32, @floatFromInt(src.g)) * opacity;
const src_b = @as(f32, @floatFromInt(src.b)) * opacity;
const inv_a = 1.0 - a;
dst.r = @intFromFloat(std.math.clamp(src_r + inv_a * @as(f32, @floatFromInt(dst.r)), 0, 255));
dst.g = @intFromFloat(std.math.clamp(src_g + inv_a * @as(f32, @floatFromInt(dst.g)), 0, 255));
dst.b = @intFromFloat(std.math.clamp(src_b + inv_a * @as(f32, @floatFromInt(dst.b)), 0, 255));
dst.a = @intFromFloat(std.math.clamp(a * 255 + inv_a * @as(f32, @floatFromInt(dst.a)), 0, 255));
}
}
/// Пиксель в локальных координатах (трансформ + PMA).
pub fn blendPixelLocal(self: *DrawContext, local_x: f32, local_y: f32, color: Color.PMA) void {
const w = self.localToWorld(local_x, local_y);