Дополнительный буфер для отрисовки broken line
This commit is contained in:
@@ -182,7 +182,7 @@ pub fn renderDocument(self: *CpuRenderEngine, document: *const Document, canvas_
|
|||||||
defer self._allocator.free(pixels);
|
defer self._allocator.free(pixels);
|
||||||
|
|
||||||
for (pixels) |*p| p.* = .{ .r = 255, .g = 255, .b = 255, .a = 255 };
|
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);
|
return try dvui.textureCreate(pixels, width, height, .nearest);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,30 @@ const Object = Document.Object;
|
|||||||
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
|
||||||
const default_thickness: f32 = 2.0;
|
const default_thickness: f32 = 2.0;
|
||||||
|
|
||||||
/// Ломаная по точкам, обводка stroke_rgba.
|
/// Ломаная по точкам, обводка stroke_rgba
|
||||||
pub fn draw(ctx: *DrawContext, obj: *const Object) void {
|
pub fn draw(
|
||||||
|
ctx: *DrawContext,
|
||||||
|
obj: *const Object,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
) !void {
|
||||||
const p_prop = obj.getProperty(.points) orelse return;
|
const p_prop = obj.getProperty(.points) orelse return;
|
||||||
const pts = p_prop.points.items;
|
const pts = p_prop.points.items;
|
||||||
if (pts.len < 2) return;
|
if (pts.len < 2) return;
|
||||||
const stroke = if (obj.getProperty(.stroke_rgba)) |s| pipeline.rgbaToPma(s.stroke_rgba) else default_stroke;
|
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 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;
|
var i: usize = 0;
|
||||||
while (i + 1 < pts.len) : (i += 1) {
|
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(©_ctx, pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, stroke, thickness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.compositeDrawerContext(©_ctx, copy_ctx.transform.opacity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ fn isVisible(obj: *const Object) bool {
|
|||||||
return if (obj.getProperty(.visible)) |p| p.visible else true;
|
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;
|
if (!isVisible(obj)) return;
|
||||||
const local = getLocalTransform(obj);
|
const local = getLocalTransform(obj);
|
||||||
const world = Transform.compose(parent_transform, local);
|
const world = Transform.compose(parent_transform, local);
|
||||||
@@ -39,16 +44,17 @@ fn drawObject(ctx: *DrawContext, obj: *const Object, parent_transform: Transform
|
|||||||
switch (obj.shape) {
|
switch (obj.shape) {
|
||||||
.line => line.draw(ctx, obj),
|
.line => line.draw(ctx, obj),
|
||||||
.ellipse => ellipse.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),
|
.arc => arc.draw(ctx, obj),
|
||||||
}
|
}
|
||||||
|
|
||||||
for (obj.children.items) |*child| {
|
for (obj.children.items) |*child| {
|
||||||
drawObject(ctx, child, world);
|
try drawObject(ctx, child, world, allocator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Рекурсивно рисует документ в буфер (объекты и потомки по порядку).
|
/// Рекурсивно рисует документ в буфер (объекты и потомки по порядку).
|
||||||
|
/// allocator опционален; если передан, ломаные рисуются через слой (без двойного наложения при alpha < 1).
|
||||||
pub fn drawDocument(
|
pub fn drawDocument(
|
||||||
pixels: []@import("dvui").Color.PMA,
|
pixels: []@import("dvui").Color.PMA,
|
||||||
buf_width: u32,
|
buf_width: u32,
|
||||||
@@ -56,7 +62,8 @@ pub fn drawDocument(
|
|||||||
visible_rect: Rect_i,
|
visible_rect: Rect_i,
|
||||||
document: *const Document,
|
document: *const Document,
|
||||||
canvas_size: Size_i,
|
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_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;
|
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{};
|
const identity = Transform{};
|
||||||
for (document.objects.items) |*obj| {
|
for (document.objects.items) |*obj| {
|
||||||
drawObject(&ctx, obj, identity);
|
try drawObject(&ctx, obj, identity, allocator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ pub const DrawContext = struct {
|
|||||||
scale_x: f32,
|
scale_x: f32,
|
||||||
scale_y: f32,
|
scale_y: f32,
|
||||||
transform: Transform = .{},
|
transform: Transform = .{},
|
||||||
|
/// Если true, blendPixelAtBuffer перезаписывает пиксель без бленда
|
||||||
|
replace_mode: bool = false,
|
||||||
|
|
||||||
pub fn setTransform(self: *DrawContext, t: Transform) void {
|
pub fn setTransform(self: *DrawContext, t: Transform) void {
|
||||||
self.transform = t;
|
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 {
|
pub fn blendPixelAtBuffer(self: *DrawContext, bx: u32, by: u32, color: Color.PMA) void {
|
||||||
if (bx >= self.buf_width or by >= self.buf_height) return;
|
if (bx >= self.buf_width or by >= self.buf_height) return;
|
||||||
const t = &self.transform;
|
|
||||||
const idx = by * self.buf_width + bx;
|
const idx = by * self.buf_width + bx;
|
||||||
const dst = &self.pixels[idx];
|
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 a = @as(f32, @floatFromInt(color.a)) / 255.0 * t.opacity;
|
||||||
const src_r = @as(f32, @floatFromInt(color.r)) * t.opacity;
|
const src_r = @as(f32, @floatFromInt(color.r)) * t.opacity;
|
||||||
const src_g = @as(f32, @floatFromInt(color.g)) * 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));
|
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).
|
/// Пиксель в локальных координатах (трансформ + PMA).
|
||||||
pub fn blendPixelLocal(self: *DrawContext, local_x: f32, local_y: f32, color: Color.PMA) void {
|
pub fn blendPixelLocal(self: *DrawContext, local_x: f32, local_y: f32, color: Color.PMA) void {
|
||||||
const w = self.localToWorld(local_x, local_y);
|
const w = self.localToWorld(local_x, local_y);
|
||||||
|
|||||||
Reference in New Issue
Block a user