diff --git a/src/font/sprite.zig b/src/font/sprite.zig index d9c6bd921..e548cd4e9 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -1,4 +1,5 @@ const std = @import("std"); +pub const Canvas = @import("sprite/Canvas.zig"); pub const Face = @import("sprite/Face.zig"); /// Sprites are represented as special codepoints outside of the Unicode diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig index 7f3208c89..ee59a5bf8 100644 --- a/src/font/sprite/Box.zig +++ b/src/font/sprite/Box.zig @@ -61,66 +61,15 @@ pub fn renderGlyph( atlas: *Atlas, cp: u32, ) !font.Glyph { - assert(atlas.format == .greyscale); + // Create the canvas we'll use to draw + var canvas = try font.sprite.Canvas.init(alloc, self.width, self.height); + defer canvas.deinit(alloc); - // 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(self.width); - const len = @intCast(usize, stride * @intCast(c_int, self.height)); + // Perform the actual drawing + try self.draw(alloc, canvas.image, cp); - // 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); - defer 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, self.width), - @intCast(c_int, self.height), - data.ptr, - stride, - ); - defer _ = img.unref(); - - try self.draw(alloc, img, cp); - - // Reserve our region in the atlas and render the glyph to it. - const region = try atlas.reserve(alloc, self.width, self.height); - if (region.width > 0 and region.height > 0) { - // Convert our []u32 to []u8 since we use 8bpp formats - assert(format.bpp() == 8); - const data_u8 = @alignCast(@alignOf(u8), @ptrCast([*]u8, data.ptr)[0..len]); - - const depth = atlas.format.depth(); - - // 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 = !(self.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, self.width * self.height * depth); - var dst_ptr = temp; - var src_ptr = data_u8.ptr; - var i: usize = 0; - while (i < self.height) : (i += 1) { - std.mem.copy(u8, dst_ptr, src_ptr[0 .. self.width * depth]); - dst_ptr = dst_ptr[self.width * depth ..]; - src_ptr += @intCast(usize, stride); - } - break :buffer temp; - } else data_u8[0..(self.width * self.height * depth)]; - defer if (buffer.ptr != data_u8.ptr) alloc.free(buffer); - - // Write the glyph information into the atlas - assert(region.width == self.width); - assert(region.height == self.height); - atlas.set(region, buffer); - } + // Write the drawing to the atlas + const region = try canvas.writeAtlas(alloc, atlas); // Our coordinates start at the BOTTOM for our renderers so we have to // specify an offset of the full height because we rendered a full size @@ -1428,19 +1377,19 @@ fn draw_right_half_block(self: Box, img: *pixman.Image) void { } fn draw_pixman_shade(self: Box, img: *pixman.Image, v: u16) void { - const rects = &[_]pixman.Rectangle16{ + const boxes = &[_]pixman.Box32{ .{ - .x = 0, - .y = 0, - .width = @intCast(u16, self.width), - .height = @intCast(u16, self.height), + .x1 = 0, + .y1 = 0, + .x2 = @intCast(i32, self.width), + .y2 = @intCast(i32, self.height), }, }; - img.fillRectangles( + img.fillBoxes( .src, .{ .red = 0, .green = 0, .blue = 0, .alpha = v }, - rects, + boxes, ) catch {}; } diff --git a/src/font/sprite/Canvas.zig b/src/font/sprite/Canvas.zig new file mode 100644 index 000000000..520615d57 --- /dev/null +++ b/src/font/sprite/Canvas.zig @@ -0,0 +1,93 @@ +//! 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"); + +image: *pixman.Image, +data: []u32, + +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; +}