Заливка и замкнутая фигура

This commit is contained in:
2026-03-03 14:21:55 +03:00
parent e5b8e6735d
commit 32cffb757d
5 changed files with 181 additions and 1 deletions

View File

@@ -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);
}
}
}
}
};