lib/filigree/src/render/canvas.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const font = @import("../font/root.zig");
3
4 pub const Color = struct {
5 r: u8 = 255,
6 g: u8 = 255,
7 b: u8 = 255,
8 a: u8 = 255,
9 };
10
11 pub const Canvas = struct {
12 pixels: []u8,
13 width: usize,
14 height: usize,
15 stride: usize,
16
17 pub fn init(pixels: []u8, width: usize, height: usize, stride: usize) !Canvas {
18 if (width == 0 or height == 0) return error.InvalidCanvas;
19 const row_bytes = try std.math.mul(usize, width, 4);
20 if (stride < row_bytes) return error.InvalidCanvas;
21 const required = try std.math.add(
22 usize,
23 try std.math.mul(usize, stride, height - 1),
24 row_bytes,
25 );
26 if (pixels.len < required) return error.InvalidCanvas;
27 return .{
28 .pixels = pixels,
29 .width = width,
30 .height = height,
31 .stride = stride,
32 };
33 }
34
35 fn blend(self: Canvas, x: i32, y: i32, color: Color, coverage: u8) void {
36 if (coverage == 0 or color.a == 0) return;
37 if (x < 0 or y < 0) return;
38 const ux: usize = @intCast(x);
39 const uy: usize = @intCast(y);
40 if (ux >= self.width or uy >= self.height) return;
41 const src_a: u32 = (@as(u32, coverage) * @as(u32, color.a) + 127) / 255;
42 if (src_a == 0) return;
43 const offset = uy * self.stride + ux * 4;
44 const dst_a: u32 = self.pixels[offset + 3];
45 const inv_src_a: u32 = 255 - src_a;
46 const out_a: u32 = src_a + (dst_a * inv_src_a) / 255;
47 if (out_a == 0) {
48 self.pixels[offset] = 0;
49 self.pixels[offset + 1] = 0;
50 self.pixels[offset + 2] = 0;
51 self.pixels[offset + 3] = 0;
52 return;
53 }
54 self.pixels[offset] = blendChannel(color.r, self.pixels[offset], src_a, dst_a, inv_src_a, out_a);
55 self.pixels[offset + 1] = blendChannel(color.g, self.pixels[offset + 1], src_a, dst_a, inv_src_a, out_a);
56 self.pixels[offset + 2] = blendChannel(color.b, self.pixels[offset + 2], src_a, dst_a, inv_src_a, out_a);
57 self.pixels[offset + 3] = @intCast(@min(out_a, 255));
58 }
59 };
60
61 pub fn blitBitmap(canvas: Canvas, bitmap: font.GlyphBitmap, dst_x: i32, dst_y: i32, color: Color) void {
62 if (bitmap.width <= 0 or bitmap.height <= 0 or bitmap.alpha.len == 0) return;
63 var row: i32 = 0;
64 while (row < bitmap.height) : (row += 1) {
65 var col: i32 = 0;
66 while (col < bitmap.width) : (col += 1) {
67 const src_index = @as(usize, @intCast(row)) * @as(usize, @intCast(bitmap.width)) + @as(usize, @intCast(col));
68 canvas.blend(dst_x + col, dst_y + row, color, bitmap.alpha[src_index]);
69 }
70 }
71 }
72
73 fn blendChannel(src: u8, dst: u8, src_a: u32, dst_a: u32, inv_src_a: u32, out_a: u32) u8 {
74 const value = (@as(u32, src) * src_a + @as(u32, dst) * dst_a * inv_src_a / 255) / out_a;
75 return @intCast(@min(value, 255));
76 }