diff --git a/README.md b/README.md index d258198..e9c3445 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Zignal - + Zignal is an image processing library heavily inspired by the amazing [dlib](http://dlib.net). @@ -48,6 +48,7 @@ They can be accessed from [here](https://bfactory-ai.github.io/zignal/examples/) Currently, there are examples for: - [Color space conversions](https://bfactory-ai.github.io/zignal/examples/colorspace.html) - [Face alignment](https://bfactory-ai.github.io/zignal/examples/face-alignment.html) +- [Perlin noise generation](https://bfactory-ai.github.io/zignal/examples/perlin.html) ## Acknowledgements diff --git a/examples/build.zig b/examples/build.zig index 7363683..1743fca 100644 --- a/examples/build.zig +++ b/examples/build.zig @@ -3,8 +3,9 @@ const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - _ = buildModule(b, "face_alignment", target, optimize); _ = buildModule(b, "colorspace", target, optimize); + _ = buildModule(b, "face_alignment", target, optimize); + _ = buildModule(b, "perlin", target, optimize); const fmt_step = b.step("fmt", "Run zig fmt"); const fmt = b.addFmt(.{ diff --git a/examples/lib/index.html b/examples/lib/index.html index b3512bb..99d46f9 100644 --- a/examples/lib/index.html +++ b/examples/lib/index.html @@ -15,6 +15,7 @@

Examples

diff --git a/examples/lib/main.js b/examples/lib/main.js deleted file mode 100644 index de0b8cf..0000000 --- a/examples/lib/main.js +++ /dev/null @@ -1,2 +0,0 @@ -(function () { -})(); diff --git a/examples/lib/perlin.html b/examples/lib/perlin.html new file mode 100644 index 0000000..30d7077 --- /dev/null +++ b/examples/lib/perlin.html @@ -0,0 +1,44 @@ + + + + + Perlin Noise + + + + +

Perlin Noise

+
+
+ Settings +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+ + diff --git a/examples/lib/perlin.js b/examples/lib/perlin.js new file mode 100644 index 0000000..05ddf41 --- /dev/null +++ b/examples/lib/perlin.js @@ -0,0 +1,79 @@ +(function() { + let wasm_promise = fetch("perlin.wasm"); + var wasm_exports = null; + const text_decoder = new TextDecoder(); + + function decodeString(ptr, len) { + if (len === 0) return ""; + return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); + } + + WebAssembly.instantiateStreaming(wasm_promise, { + js: { + log: function(ptr, len) { + const msg = decodeString(ptr, len); + console.log(msg); + }, + now: function() { + return performance.now(); + }, + }, + }).then(function(obj) { + wasm_exports = obj.instance.exports; + window.wasm = obj; + console.log("wasm loaded"); + const canvas = document.getElementById("canvas"); + const ctx = canvas.getContext("2d"); + const rows = 512; + const cols = 512; + const rgbaSize = rows * cols * 4; + + const amplitudeRange = document.getElementById("amplitude-range"); + amplitudeRange.oninput = function() { + document.getElementById("amplitude").innerHTML = amplitudeRange.value; + generateNoise(); + } + const frequencyRange = document.getElementById("frequency-range"); + frequencyRange.oninput = function() { + document.getElementById("frequency").innerHTML = frequencyRange.value; + generateNoise(); + } + const octavesRange = document.getElementById("octaves-range"); + octavesRange.oninput = function() { + document.getElementById("octaves").innerHTML = octavesRange.value; + generateNoise(); + } + const persistenceRange = document.getElementById("persistence-range"); + persistenceRange.oninput = function() { + document.getElementById("persistence").innerHTML = persistenceRange.value; + generateNoise(); + } + const lacunarityRange = document.getElementById("lacunarity-range"); + lacunarityRange.oninput = function() { + document.getElementById("lacunarity").innerHTML = lacunarityRange.value; + generateNoise(); + } + + function generateNoise() { + document.getElementById("amplitude").innerHTML = amplitudeRange.value; + wasm_exports.set_amplitude(amplitudeRange.value); + document.getElementById("frequency").innerHTML = frequencyRange.value; + wasm_exports.set_frequency(frequencyRange.value); + document.getElementById("octaves").innerHTML = octavesRange.value; + wasm_exports.set_octaves(octavesRange.value); + document.getElementById("persistence").innerHTML = persistenceRange.value; + wasm_exports.set_persistence(persistenceRange.value); + document.getElementById("lacunarity").innerHTML = lacunarityRange.value; + wasm_exports.set_lacunarity(lacunarityRange.value); + + const rgbaPtr = wasm_exports.alloc(rgbaSize); + const rgba = new Uint8ClampedArray(wasm_exports.memory.buffer, rgbaPtr, rgbaSize); + wasm_exports.generate(rgbaPtr, rows, cols); + let image = ctx.getImageData(0, 0, cols, rows); + image.data.set(rgba); + ctx.putImageData(image, 0, 0); + wasm_exports.free(rgbaPtr, rgbaSize); + } + generateNoise(); + }); +})(); diff --git a/examples/lib/styles.css b/examples/lib/styles.css index 7031600..7d1430a 100644 --- a/examples/lib/styles.css +++ b/examples/lib/styles.css @@ -10,12 +10,6 @@ position: relative; } -.grid-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - grid-gap: 20px; -} - #form { width: 300px; margin: 10px; @@ -30,6 +24,10 @@ top: 0px; } +#canvas { + border: 1px solid black; +} + #video { position: absolute; } diff --git a/examples/src/js.zig b/examples/src/js.zig index bd82bbd..179f705 100644 --- a/examples/src/js.zig +++ b/examples/src/js.zig @@ -1,7 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; -const gpa = std.heap.wasm_allocator; pub const js = struct { extern "js" fn log(ptr: [*]const u8, len: usize) void; @@ -29,10 +28,10 @@ pub fn logFn( } export fn alloc(len: usize) [*]u8 { - const slice = gpa.alloc(u8, len) catch @panic("OOM"); + const slice = std.heap.wasm_allocator.alloc(u8, len) catch @panic("OOM"); return slice.ptr; } export fn free(ptr: [*]const u8, len: usize) void { - gpa.free(ptr[0..len]); + std.heap.wasm_allocator.free(ptr[0..len]); } diff --git a/examples/src/perlin.zig b/examples/src/perlin.zig new file mode 100644 index 0000000..bd7206f --- /dev/null +++ b/examples/src/perlin.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const perlin = @import("zignal").perlin; +const Rgba = @import("zignal").Rgba; +const Image = @import("zignal").Image; + +pub const std_options: std.Options = .{ + .logFn = if (builtin.cpu.arch.isWasm()) @import("js.zig").logFn else std.log.defaultLog, + .log_level = .info, +}; + +var opts = perlin.Options(f32){ + .amplitude = 1, + .frequency = 1, + .octaves = 1, + .persistence = 0.5, + .lacunarity = 2, +}; + +pub export fn set_amplitude(val: f32) void { + std.log.debug("setting amplitude to {d}", .{val}); + opts.amplitude = val; +} +pub export fn set_frequency(val: f32) void { + std.log.debug("setting frequency to {d}", .{val}); + opts.frequency = val; +} + +pub export fn set_octaves(val: usize) void { + std.log.debug("setting octaves to {d}", .{val}); + opts.octaves = val; +} + +pub export fn set_persistence(val: f32) void { + std.log.debug("setting persistence to {d}", .{val}); + opts.persistence = val; +} + +pub export fn set_lacunarity(val: f32) void { + std.log.debug("setting lacunarity to {d}", .{val}); + opts.lacunarity = val; +} + +pub export fn generate(rgba_ptr: [*]Rgba, rows: usize, cols: usize) void { + const size = rows * cols; + const image = Image(Rgba).init(rows, cols, rgba_ptr[0..size]); + for (0..image.rows) |r| { + const y: f32 = @as(f32, @floatFromInt(r)) / @as(f32, @floatFromInt(image.rows)); + for (0..image.cols) |c| { + const pos = r * image.cols + c; + const x: f32 = @as(f32, @floatFromInt(c)) / @as(f32, @floatFromInt(image.cols)); + const val: u8 = @intFromFloat( + @max(0, @min(255, @round( + 255 * (opts.amplitude / 2 * (perlin.generate(f32, x, y, 0, opts) + opts.amplitude)), + ))), + ); + image.data[pos] = Rgba.fromGray(val, 255); + } + } +}