mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-24 12:46:10 +03:00
132 lines
4.0 KiB
Zig
132 lines
4.0 KiB
Zig
//! This exposes primitives to draw 2D graphics and export the graphic to
|
|
//! a font atlas.
|
|
const Canvas = @This();
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const pixman = @import("pixman");
|
|
const Atlas = @import("../../Atlas.zig");
|
|
|
|
/// The underlying image.
|
|
image: *pixman.Image,
|
|
|
|
/// The raw data buffer.
|
|
data: []u32,
|
|
|
|
pub const Rect = struct {
|
|
x: u32,
|
|
y: u32,
|
|
width: u32,
|
|
height: u32,
|
|
};
|
|
|
|
/// We only use alpha-channel so a pixel can only be "on" or "off".
|
|
pub const Color = enum {
|
|
on,
|
|
off,
|
|
|
|
fn pixmanColor(self: Color) pixman.Color {
|
|
return switch (self) {
|
|
.on => .{ .red = 0xFFFF, .green = 0xFFFF, .blue = 0xFFFF, .alpha = 0xFFFF },
|
|
.off => .{ .red = 0, .green = 0, .blue = 0, .alpha = 0 },
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn init(alloc: Allocator, width: u32, height: u32) !Canvas {
|
|
// Determine the config for our image buffer. The images we draw
|
|
// for boxes are always 8bpp
|
|
const format: pixman.FormatCode = .a8;
|
|
const stride = format.strideForWidth(width);
|
|
const len = @intCast(usize, stride * @intCast(c_int, height));
|
|
|
|
// Allocate our buffer. pixman uses []u32 so we divide our length
|
|
// by 4 since u32 / u8 = 4.
|
|
var data = try alloc.alloc(u32, len / 4);
|
|
errdefer alloc.free(data);
|
|
std.mem.set(u32, data, 0);
|
|
|
|
// Create the image we'll draw to
|
|
const img = try pixman.Image.createBitsNoClear(
|
|
format,
|
|
@intCast(c_int, width),
|
|
@intCast(c_int, height),
|
|
data.ptr,
|
|
stride,
|
|
);
|
|
errdefer _ = img.unref();
|
|
|
|
return Canvas{
|
|
.image = img,
|
|
.data = data,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Canvas, alloc: Allocator) void {
|
|
alloc.free(self.data);
|
|
_ = self.image.unref();
|
|
self.* = undefined;
|
|
}
|
|
|
|
/// Write the data in this drawing to the atlas.
|
|
pub fn writeAtlas(self: *Canvas, alloc: Allocator, atlas: *Atlas) !Atlas.Region {
|
|
assert(atlas.format == .greyscale);
|
|
|
|
const width = @intCast(u32, self.image.getWidth());
|
|
const height = @intCast(u32, self.image.getHeight());
|
|
const region = try atlas.reserve(alloc, width, height);
|
|
if (region.width > 0 and region.height > 0) {
|
|
const depth = atlas.format.depth();
|
|
|
|
// Convert our []u32 to []u8 since we use 8bpp formats
|
|
const stride = self.image.getStride();
|
|
const data = @alignCast(
|
|
@alignOf(u8),
|
|
@ptrCast([*]u8, self.data.ptr)[0 .. self.data.len * 4],
|
|
);
|
|
|
|
// We can avoid a buffer copy if our atlas width and bitmap
|
|
// width match and the bitmap pitch is just the width (meaning
|
|
// the data is tightly packed).
|
|
const needs_copy = !(width * depth == stride);
|
|
|
|
// If we need to copy the data, we copy it into a temporary buffer.
|
|
const buffer = if (needs_copy) buffer: {
|
|
var temp = try alloc.alloc(u8, width * height * depth);
|
|
var dst_ptr = temp;
|
|
var src_ptr = data.ptr;
|
|
var i: usize = 0;
|
|
while (i < height) : (i += 1) {
|
|
std.mem.copy(u8, dst_ptr, src_ptr[0 .. width * depth]);
|
|
dst_ptr = dst_ptr[width * depth ..];
|
|
src_ptr += @intCast(usize, stride);
|
|
}
|
|
break :buffer temp;
|
|
} else data[0..(width * height * depth)];
|
|
defer if (buffer.ptr != data.ptr) alloc.free(buffer);
|
|
|
|
// Write the glyph information into the atlas
|
|
assert(region.width == width);
|
|
assert(region.height == height);
|
|
atlas.set(region, buffer);
|
|
}
|
|
|
|
return region;
|
|
}
|
|
|
|
/// Draw and fill a rectangle. This is the main primitive for drawing
|
|
/// lines as well (which are just generally skinny rectangles...)
|
|
pub fn rect(self: *Canvas, v: Rect, color: Color) void {
|
|
const boxes = &[_]pixman.Box32{
|
|
.{
|
|
.x1 = @intCast(i32, v.x),
|
|
.y1 = @intCast(i32, v.y),
|
|
.x2 = @intCast(i32, v.x + v.width),
|
|
.y2 = @intCast(i32, v.y + v.height),
|
|
},
|
|
};
|
|
|
|
self.image.fillBoxes(.src, color.pixmanColor(), boxes) catch {};
|
|
}
|