|
| 1 | +const std = @import("std"); |
| 2 | +const exp = std.math.exp; |
| 3 | +const pi = std.math.pi; |
| 4 | +const log = std.log; |
| 5 | +const cairo = @import("cairo"); |
| 6 | +const Format = cairo.image_surface.Format; |
| 7 | +const setBackground = @import("utils.zig").setBackground; |
| 8 | + |
| 9 | +/// https://www.cairographics.org/samples/image/ |
| 10 | +fn image(cr: *cairo.Context) !void { |
| 11 | + var surface = try cairo.Surface.createFromPng("data/romedalen.png"); |
| 12 | + defer surface.destroy(); |
| 13 | + |
| 14 | + const w = try surface.getWidth(); |
| 15 | + const h = try surface.getHeight(); |
| 16 | + |
| 17 | + cr.translate(128.0, 128.0); |
| 18 | + cr.rotate(45 * pi / 180.0); |
| 19 | + cr.scale(256.0 / @intToFloat(f64, w), 256.0 / @intToFloat(f64, h)); |
| 20 | + cr.translate(-0.5 * @intToFloat(f64, w), -0.5 * @intToFloat(f64, h)); |
| 21 | + |
| 22 | + cr.setSourceSurface(&surface, 0, 0); |
| 23 | + cr.paint(); |
| 24 | +} |
| 25 | + |
| 26 | +/// https://www.cairographics.org/cookbook/blur.c/ |
| 27 | +fn blurImageSurface(allocator: *std.mem.Allocator, surface: *cairo.Surface, radius: u16) !void { |
| 28 | + try cairo.checkSurfaceStatus(surface.surface); |
| 29 | + |
| 30 | + const width = try surface.getWidth(); |
| 31 | + const height = try surface.getHeight(); |
| 32 | + const width_f64 = @intToFloat(f64, width); |
| 33 | + |
| 34 | + // check that we are starting from a Cairo image surface |
| 35 | + switch (cairo.image_surface.getFormat(surface.surface)) { |
| 36 | + Format.A1 => { |
| 37 | + return error.GaussianBlurNotImplementedForThisSurface; |
| 38 | + }, |
| 39 | + Format.A8 => log.debug("Can blur but set width /= 4;", .{}), |
| 40 | + Format.Argb32 => { |
| 41 | + log.debug("Ok, can blur", .{}); |
| 42 | + }, |
| 43 | + Format.Rgb24 => { |
| 44 | + log.debug("Ok, can blur", .{}); |
| 45 | + }, |
| 46 | + else => |format| { |
| 47 | + log.debug("Format: {}", .{format}); |
| 48 | + return error.GaussianBlurNotImplementedForThisSurface; |
| 49 | + }, |
| 50 | + } |
| 51 | + |
| 52 | + var tmp = try cairo.image_surface.create(Format.Argb32, width, height); |
| 53 | + // defer tmp.destroy(); |
| 54 | + try cairo.checkSurfaceStatus(tmp); |
| 55 | + |
| 56 | + const src = try cairo.image_surface.getData(surface.surface); |
| 57 | + // log.debug("src {}", .{src}); |
| 58 | + const src_stride = cairo.image_surface.getStride(surface.surface); |
| 59 | + // log.debug("src_stride {}", .{src_stride}); |
| 60 | + |
| 61 | + const dest = try cairo.image_surface.getData(tmp); |
| 62 | + const dest_stride = cairo.image_surface.getStride(tmp); |
| 63 | + |
| 64 | + // build the gaussian kernel /////////////////////////////////////////////// |
| 65 | + // TODO: add link to mathematical function |
| 66 | + const size: usize = 17; |
| 67 | + const half = @intToFloat(f64, size) / 2.0; |
| 68 | + var kernel = std.ArrayList(f64).init(allocator); |
| 69 | + var a: f64 = 0; |
| 70 | + var i: usize = 0; |
| 71 | + while (i < size) : (i += 1) { |
| 72 | + const f = @intToFloat(f64, i) - half; |
| 73 | + const coeff = exp(-f * f / 30.0) * 80.0; |
| 74 | + try kernel.append(coeff); |
| 75 | + a += coeff; |
| 76 | + } |
| 77 | + |
| 78 | + // blur the image ////////////////////////////////////////////////////////// |
| 79 | + |
| 80 | + // const s_addr = (@ptrToInt(src) + src_stride); // double-check the parentheses |
| 81 | + // const s = @intToPtr([*]u32, s_addr); |
| 82 | + // log.debug("s {}\n", .{@typeInfo(@TypeOf(s))}); |
| 83 | + |
| 84 | + // Horizontally blur from surface -> tmp |
| 85 | + i = 0; |
| 86 | + while (i < height) : (i += 1) { |
| 87 | + // log.debug("height px {}/{}", .{i+1, height}); |
| 88 | + const s_addr = (@ptrToInt(src) + i * src_stride); // double-check the parentheses |
| 89 | + const d_addr = (@ptrToInt(dest) + i * dest_stride); |
| 90 | + const s = @intToPtr([*]u32, s_addr); |
| 91 | + const d = @intToPtr([*]u32, d_addr); |
| 92 | + // std.debug.print("s {}\n", .{@typeInfo(@TypeOf(s))}); |
| 93 | + var j: usize = 0; |
| 94 | + while (j < width) : (j += 1) { |
| 95 | + // log.debug("width px {}/{}", .{j+1, width}); |
| 96 | + if (radius < j and j < (width - radius)) { |
| 97 | + // log.debug("d[j] = s[j]", .{}); |
| 98 | + d[j] = s[j]; |
| 99 | + continue; |
| 100 | + } else { |
| 101 | + // log.debug("ELSE", .{}); |
| 102 | + var x: f64 = 0; |
| 103 | + var y: f64 = 0; |
| 104 | + var z: f64 = 0; |
| 105 | + var w: f64 = 0; |
| 106 | + |
| 107 | + var k: usize = 0; |
| 108 | + while (k < size) : (k += 1) { |
| 109 | + const k_f64 = @intToFloat(f64, k); |
| 110 | + const j_f64 = @intToFloat(f64, j); |
| 111 | + if ((j_f64 - half + k_f64 < 0) or (j_f64 - half + k_f64 >= width_f64)) { |
| 112 | + continue; |
| 113 | + } else { |
| 114 | + const idx = @floatToInt(usize, j_f64 - half + k_f64); |
| 115 | + const p = s[idx]; |
| 116 | + // what are these things? What does this shifting mean? |
| 117 | + const px = @intToFloat(f64, (p >> 24) & 0xff); |
| 118 | + const py = @intToFloat(f64, (p >> 16) & 0xff); |
| 119 | + const pz = @intToFloat(f64, (p >> 8) & 0xff); |
| 120 | + const pw = @intToFloat(f64, (p >> 0) & 0xff); |
| 121 | + x += px * kernel.items[k]; |
| 122 | + y += py * kernel.items[k]; |
| 123 | + z += pz * kernel.items[k]; |
| 124 | + w += pw * kernel.items[k]; |
| 125 | + } |
| 126 | + } |
| 127 | + // d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; |
| 128 | + const a_u32 = @floatToInt(u32, a); |
| 129 | + const cond1 = x / @intToFloat(f64, (a_u32 << 24)); |
| 130 | + const cond2 = y / @intToFloat(f64, (a_u32 << 16)); |
| 131 | + const cond3 = z / @intToFloat(f64, (a_u32 << 8)); |
| 132 | + const cond4 = w / a; |
| 133 | + // d[j] = cond1 | cond2 | cond3 | cond4; |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + // TODO: Then vertically blur from tmp -> surface |
| 139 | + |
| 140 | + cairo.c.cairo_surface_destroy(tmp); |
| 141 | + cairo.c.cairo_surface_mark_dirty(surface.surface); |
| 142 | +} |
| 143 | + |
| 144 | +pub fn main() !void { |
| 145 | + const width: u16 = 256; |
| 146 | + const height: u16 = 256; |
| 147 | + std.debug.print("gaussian_blur example ({}x{} px)\n", .{ width, height }); |
| 148 | + |
| 149 | + var surface = try cairo.Surface.image(width, height); |
| 150 | + defer surface.destroy(); |
| 151 | + |
| 152 | + var cr = try cairo.Context.create(&surface); |
| 153 | + defer cr.destroy(); |
| 154 | + |
| 155 | + setBackground(&cr); |
| 156 | + try image(&cr); |
| 157 | + |
| 158 | + const radius: u16 = 20; |
| 159 | + try blurImageSurface(std.testing.allocator, &surface, radius); |
| 160 | + |
| 161 | + _ = surface.writeToPng("examples/generated/gaussian_blur.png"); |
| 162 | +} |
0 commit comments