Заливка и замкнутая фигура
This commit is contained in:
@@ -75,6 +75,7 @@ pub const DrawContext = struct {
|
||||
transform: Transform = .{},
|
||||
/// Если true, blendPixelAtBuffer перезаписывает пиксель без бленда
|
||||
replace_mode: bool = false,
|
||||
_fill_canvas: ?*FillCanvas = null,
|
||||
|
||||
pub fn setTransform(self: *DrawContext, t: Transform) void {
|
||||
self.transform = t;
|
||||
@@ -132,8 +133,10 @@ pub const DrawContext = struct {
|
||||
}
|
||||
|
||||
/// Смешивает цвет в пикселе буфера с учётом opacity трансформа. В replace_mode просто перезаписывает пиксель.
|
||||
/// Если активен fill canvas, каждый записанный пиксель помечается как граница для заливки.
|
||||
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 (self._fill_canvas) |fc| fc.setBorder(bx, by);
|
||||
const idx = by * self.buf_width + bx;
|
||||
const dst = &self.pixels[idx];
|
||||
if (self.replace_mode) {
|
||||
@@ -183,6 +186,23 @@ pub const DrawContext = struct {
|
||||
if (bx < 0 or bx >= vw or by < 0 or by >= vh) return;
|
||||
self.blendPixelAtBuffer(@intCast(bx), @intCast(by), color);
|
||||
}
|
||||
|
||||
/// Начинает сбор границ для заливки: создаёт FillCanvas и при последующих вызовах blendPixelAtBuffer помечает пиксели как границу.
|
||||
pub fn startFill(self: *DrawContext, allocator: std.mem.Allocator) !void {
|
||||
const fc = try FillCanvas.init(allocator, self.buf_width, self.buf_height);
|
||||
const ptr = try allocator.create(FillCanvas);
|
||||
ptr.* = fc;
|
||||
self._fill_canvas = ptr;
|
||||
}
|
||||
|
||||
/// Рисует заливку по собранным границам цветом color, освобождает FillCanvas и сбрасывает режим.
|
||||
pub fn stopFill(self: *DrawContext, allocator: std.mem.Allocator, color: Color.PMA) void {
|
||||
const fc = self._fill_canvas orelse return;
|
||||
self._fill_canvas = null;
|
||||
fc.fillColor(self, color);
|
||||
fc.deinit(allocator);
|
||||
allocator.destroy(fc);
|
||||
}
|
||||
};
|
||||
|
||||
/// Конвертирует u32 0xRRGGBBAA в Color.PMA.
|
||||
@@ -200,3 +220,110 @@ pub fn rgbaToPma(rgba: u32) Color.PMA {
|
||||
.a = a,
|
||||
};
|
||||
}
|
||||
|
||||
/// Контекст для заполнения фигур цветом.
|
||||
const FillCanvas = struct {
|
||||
/// Все пиксели холста в виде одного непрерывного массива.
|
||||
_pixels: []PixelType,
|
||||
/// Массив строк, каждая строка содержит свой срез пикселей и границы.
|
||||
rows: []Row,
|
||||
buf_width: u32,
|
||||
buf_height: u32,
|
||||
|
||||
const PixelType = enum(u8) {
|
||||
Empty, // Пустой пиксель
|
||||
Border, // Пиксель границы
|
||||
Fill, // Пиксель закрашен
|
||||
};
|
||||
|
||||
const Row = struct {
|
||||
pixels: []PixelType,
|
||||
first_border_x: ?u32 = null,
|
||||
last_border_x: ?u32 = null,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, width: u32, height: u32) !FillCanvas {
|
||||
const pixels = try allocator.alloc(PixelType, width * height);
|
||||
errdefer allocator.free(pixels);
|
||||
@memset(pixels, .Empty);
|
||||
|
||||
const rows = try allocator.alloc(Row, height);
|
||||
errdefer allocator.free(rows);
|
||||
|
||||
var y: u32 = 0;
|
||||
while (y < height) : (y += 1) {
|
||||
const start = y * width;
|
||||
rows[y] = .{
|
||||
.pixels = pixels[start .. start + width],
|
||||
.first_border_x = null,
|
||||
.last_border_x = null,
|
||||
};
|
||||
}
|
||||
|
||||
return .{
|
||||
._pixels = pixels,
|
||||
.rows = rows,
|
||||
.buf_width = width,
|
||||
.buf_height = height,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *FillCanvas, allocator: std.mem.Allocator) void {
|
||||
allocator.free(self.rows);
|
||||
allocator.free(self._pixels);
|
||||
}
|
||||
|
||||
pub fn setBorder(self: *FillCanvas, x: u32, y: u32) void {
|
||||
if (x >= self.buf_width or y >= self.buf_height) return;
|
||||
|
||||
const row = &self.rows[y];
|
||||
row.pixels[x] = .Border;
|
||||
|
||||
if (row.first_border_x) |first| {
|
||||
if (x < first) {
|
||||
row.first_border_x = x;
|
||||
}
|
||||
} else {
|
||||
row.first_border_x = x;
|
||||
}
|
||||
|
||||
if (row.last_border_x) |last| {
|
||||
if (x > last) {
|
||||
row.last_border_x = x;
|
||||
}
|
||||
} else {
|
||||
row.last_border_x = x;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fillColor(self: *FillCanvas, draw_ctx: *DrawContext, color: Color.PMA) void {
|
||||
// В каждой строке пройтись от первого до последнего x с учетом границ:
|
||||
var y: u32 = 0;
|
||||
while (y < self.buf_height) : (y += 1) {
|
||||
const row = &self.rows[y];
|
||||
|
||||
// Если нет хотя бы одной границы — пропустить строку
|
||||
if (row.first_border_x == null or row.last_border_x == null) continue;
|
||||
|
||||
const first = row.first_border_x.?;
|
||||
const last = row.last_border_x.?;
|
||||
|
||||
var on_border = true;
|
||||
var segment_index: usize = 0; // индекс "промежутка" (счет с 0)
|
||||
var x = first;
|
||||
while (x <= last) : (x += 1) {
|
||||
if (row.pixels[x] == .Border) {
|
||||
on_border = true;
|
||||
} else {
|
||||
if (on_border)
|
||||
segment_index += 1;
|
||||
on_border = false;
|
||||
}
|
||||
if (!on_border and row.pixels[x] == .Empty and segment_index % 2 == 1) {
|
||||
row.pixels[x] = .Fill;
|
||||
draw_ctx.blendPixelAtBuffer(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user