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 }