From: Brendan Hansen Date: Tue, 7 Sep 2021 14:11:43 +0000 (-0500) Subject: added almost all of the canvas functionality X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=0d8dbc5b27bb47a3ddeb6ac8f8fa45af7326d3d7;p=onyx-live.git added almost all of the canvas functionality --- diff --git a/canvas.onyx b/canvas.onyx index 07e830e..dbde269 100644 --- a/canvas.onyx +++ b/canvas.onyx @@ -1,13 +1,52 @@ package canvas -init :: () -> void #foreign "canvas" "init" --- -clear :: () -> void #foreign "canvas" "clear" --- -set_fill_style :: (style: str) -> void #foreign "canvas" "set_fill_style" --- -fill_rect :: (x: i32, y: i32, w: i32, h: i32) -> void #foreign "canvas" "fill_rect" --- - -get_size_internal :: (out_width: ^i32, out_height: ^i32) -> void #foreign "canvas" "get_size" --- -get_size :: () -> (i32, i32) { - sx, sy: i32; +FillRule :: enum { NonZero :: 1; EvenOdd :: 2; } +TextAlign :: enum { Left :: 1; Right; Center; Start; End; } +LineJoin :: enum { Bevel :: 1; Round; Miter; } + +init :: () -> void #foreign "canvas" "init" --- +clear :: () -> void #foreign "canvas" "clear" --- +sync :: () -> void #foreign "canvas" "sync" --- + +get_size_internal :: (out_width: ^f32, out_height: ^f32) -> void #foreign "canvas" "get_size" --- +get_size :: () -> (f32, f32) { + sx, sy: f32; get_size_internal(^sx, ^sy); return sx, sy; } + +// Styling settings +fill_style :: (style: str) -> void #foreign "canvas" "fill_style" --- +font :: (fontname: str) -> void #foreign "canvas" "font" --- +image_smoothing_enabled :: (enabled: bool) -> void #foreign "canvas" "image_smoothing_enabled" --- +line_join :: (join: LineJoin) -> void #foreign "canvas" "line_join" --- +line_width :: (width: f32) -> void #foreign "canvas" "line_width" --- +stroke_style :: (style: str) -> void #foreign "canvas" "stroke_style" --- +text_align :: (align: TextAlign) -> void #foreign "canvas" "text_align" --- + +// Drawing +arc :: (x: f32, y: f32, r: f32, start_angle: f32, end_angle: f32, counter_clockwise := false) -> void #foreign "canvas" "arc" --- +arc_to :: (x1: f32, y1: f32, x2: f32, y2: f32, radius: f32) -> void #foreign "canvas" "arc_to" --- +begin_path :: () -> void #foreign "canvas" "begin_path" --- +bezier_curve_to :: (cp1x: f32, cp1y: f32, cp2x: f32, cp2y: f32, x: f32, y: f32) -> void #foreign "canvas" "bezier_curve_to" --- +clear_rect :: (x: f32, y: f32, w: f32, h: f32) -> void #foreign "canvas" "clear_rect" --- +clip :: (clip_rule := FillRule.NonZero) -> void #foreign "canvas" "clip" --- +close_path :: () -> void #foreign "canvas" "close_path" --- +ellipse :: (x: f32, y: f32, rx: f32, ry: f32, rotation: f32, start_angle: f32, end_angle: f32, counter_clockwise := false) -> void #foreign "canvas" "ellipse" --- +fill :: (fill_rule := FillRule.NonZero) -> void #foreign "canvas" "fill" --- +fill_rect :: (x: f32, y: f32, w: f32, h: f32) -> void #foreign "canvas" "fill_rect" --- +fill_text :: (text: str, x: f32, y: f32, max_width := -1.0f) -> void #foreign "canvas" "fill_text" --- +line_to :: (x: f32, y: f32) -> void #foreign "canvas" "line_to" --- +move_to :: (x: f32, y: f32) -> void #foreign "canvas" "move_to" --- +quadratic_curve_to :: (cpx: f32, cpy: f32, x: f32, y: f32) -> void #foreign "canvas" "quadratic_curve_to" --- +rect :: (x: f32, y: f32, w: f32, h: f32) -> void #foreign "canvas" "rect" --- +restore :: () -> void #foreign "canvas" "restore" --- +rotate :: (angle: f32) -> void #foreign "canvas" "rotate" --- +save :: () -> void #foreign "canvas" "save" --- +scale :: (x: f32, y: f32) -> void #foreign "canvas" "scale" --- +set_transform :: (a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> void #foreign "canvas" "set_transform" --- +stroke :: () -> void #foreign "canvas" "stroke" --- +stroke_rect :: (x: f32, y: f32, w: f32, h: f32) -> void #foreign "canvas" "stroke_rect" --- +stroke_text :: (text: str, x: f32, y: f32, max_width := -1.0f) -> void #foreign "canvas" "stroke_text" --- +transform :: (a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> void #foreign "canvas" "transform" --- +translate :: (x: f32, y: f32) -> void #foreign "canvas" "translate" --- diff --git a/static/src/canvas.js b/static/src/canvas.js index fa9c98f..a880bfb 100644 --- a/static/src/canvas.js +++ b/static/src/canvas.js @@ -1,7 +1,7 @@ let canvas_element = ""; let ctx = null; -function update_canvas_size() { +function update_canvas_data() { let canvas_data = new Int32Array(canvas_shared_buffer); let box = canvas_element.getBoundingClientRect(); @@ -19,28 +19,50 @@ function canvas_handler(instr) { case 'init': { canvas_element = document.getElementById("render-target"); ctx = canvas_element.getContext('2d'); - update_canvas_size(); + update_canvas_data(); break; } - case 'set_fill_style': { - ctx.fillStyle = instr[1]; + case 'sync': { + window.requestAnimationFrame(() => { + let canvas_data = new Int32Array(canvas_shared_buffer); + Atomics.store(canvas_data, 2, 1); + Atomics.notify(canvas_data, 2); + }); break; } - case 'fill_rect': { - ctx.fillRect(instr[1], instr[2], instr[3], instr[4]); - let canvas_data = new Int32Array(canvas_shared_buffer); + case 'fill_style': ctx.fillStyle = instr[1]; break; + case 'font': ctx.font = instr[1]; break; + case 'image_smoothing_enabled': ctx.imageSmoothingEnabled = instr[1]; break; + case 'line_join': ctx.lineJoin = instr[1]; break; + case 'line_width': ctx.lineWidth = instr[1]; break; + case 'stroke_style': ctx.strokeStyle = instr[1]; break; + case 'text_align': ctx.textAlign = instr[1]; break; - // TODO: Do I need to synchonize the worker thread and the master thread? - Atomics.store(canvas_data, 2, 1); - Atomics.notify(canvas_data, 2); - break; - } - - case 'clear': { - ctx.clearRect(0, 0, canvas_element.width, canvas_element.height); - break; - } + case 'arc': ctx.arc(instr[1], instr[2], instr[3], instr[4], instr[5], instr[6]); break; + case 'arc_to': ctx.arcTo(instr[1], instr[2], instr[3], instr[4], instr[5]); break; + case 'begin_path': ctx.beginPath(); break; + case 'clear': ctx.clearRect(0, 0, canvas_element.width, canvas_element.height); break; + case 'clear_rect': ctx.clearRect(instr[1], instr[2], instr[3], instr[4]); break; + case 'clip': ctx.clip(instr[1]); break; + case 'close_path': ctx.closePath(); break; + case 'ellipse': ctx.ellipse(instr[1], instr[2], instr[3], instr[4], instr[5], instr[6], instr[7], instr[8]); break; + case 'fill': ctx.fill(instr[1]); break; + case 'fill_rect': ctx.fillRect(instr[1], instr[2], instr[3], instr[4]); break; + case 'fill_text': ctx.fillText(instr[1], instr[2], instr[3], instr[4]); break; + case 'line_to': ctx.lineTo(instr[1], instr[2]); break; + case 'move_to': ctx.moveTo(instr[1], instr[2]); break; + case 'quadratic_curve_to': ctx.quadraticCurveTo(instr[1], instr[2], instr[3], instr[4]); break; + case 'rect': ctx.rect(instr[1], instr[2], instr[3], instr[4]); break; + case 'rotate': ctx.rotate(instr[1]); break; + case 'save': ctx.save(); break; + case 'scale': ctx.scale(instr[1], instr[2]); break; + case 'set_transform': ctx.setTransform(instr[1], instr[2], instr[3], instr[4], instr[5], instr[6]); break; + case 'stroke': ctx.stroke(); break; + case 'stroke_rect': ctx.strokeRect(instr[1], instr[2], instr[3], instr[4]); break; + case 'stroke_text': ctx.strokeText(instr[1], instr[2], instr[3], instr[4]); break; + case 'transform': ctx.transform(instr[1], instr[2], instr[3], instr[4], instr[5], instr[6]); break; + case 'translate': ctx.translate(instr[1], instr[2]); break; } } diff --git a/static/src/worker.js b/static/src/worker.js index 70bd4e3..46c2a9d 100644 --- a/static/src/worker.js +++ b/static/src/worker.js @@ -2,11 +2,15 @@ let input_shared_buffer = null; let canvas_data_buffer = null; let wasm_memory = null; +function S(strptr, strlen) { + let strdata = new Uint8Array(wasm_memory.buffer, strptr, strlen); + return new TextDecoder().decode(strdata); +} + let import_obj = { host: { print_str(strptr, strlen) { - let strdata = new Uint8Array(wasm_memory.buffer, strptr, strlen); - let str = new TextDecoder().decode(strdata); + let str = S(strptr, strlen); self.postMessage({ type: 'print_str', data: str }); }, @@ -45,37 +49,110 @@ let import_obj = { init() { self.postMessage({ type: 'canvas', data: [ 'init' ] }); + // Issue a synchronization because the data needs to be correct + // before control should be handed back to the running program. let canvas_data = new Int32Array(canvas_data_buffer); Atomics.wait(canvas_data, 2, 0); Atomics.store(canvas_data, 2, 0); }, - clear() { - self.postMessage({ type: 'canvas', data: [ 'clear' ] }); + sync() { + self.postMessage({ type: 'canvas', data: [ 'sync'] }); + + let canvas_data = new Int32Array(canvas_data_buffer); + Atomics.wait(canvas_data, 2, 0); + Atomics.store(canvas_data, 2, 0); }, get_size(outwidth, outheight) { let data = new DataView(wasm_memory.buffer); let canvas_data = new Int32Array(canvas_data_buffer); - data.setInt32(outwidth, Atomics.load(canvas_data, 0), true); - data.setInt32(outheight, Atomics.load(canvas_data, 1), true); + data.setFloat32(outwidth, Atomics.load(canvas_data, 0), true); + data.setFloat32(outheight, Atomics.load(canvas_data, 1), true); }, - set_fill_style(styleptr, stylelen) { - let strdata = new Uint8Array(wasm_memory.buffer, styleptr, stylelen); - let str = new TextDecoder().decode(strdata); + // Settings + fill_style(styleptr, stylelen) { self.postMessage({ type: 'canvas', data: [ 'fill_style', S(styleptr, stylelen) ] }); }, + font(p, l) { self.postMessage({ type: 'canvas', data: [ 'font', S(p, l) ] }); }, + image_smoothing_enabled(e) { self.postMessage({ type: 'canvas', data: [ 'image_smoothing_enabled', e != 0 ] }); }, + line_join(join) { + let method = ""; + switch (join) { + case 1: method = "bevel"; break; + case 2: method = "round"; break; + case 3: method = "miter"; break; + } - self.postMessage({ type: 'canvas', data: [ 'set_fill_style', str ] }); + if (method != "") { + self.postMessage({ type: 'canvas', data: [ 'line_join' , method ] }); + } }, + line_width(width) { self.postMessage({ type: 'canvas', data: [ 'line_width', width ] }); }, + stroke_style(s, l) { self.postMessage({ type: 'canvas', data: [ 'stroke_style', S(s, l) ] }); }, + text_align(align) { + let method = ""; + switch (align) { + case 1: method = "left"; break; + case 2: method = "right"; break; + case 3: method = "center"; break; + case 4: method = "start"; break; + case 5: method = "end"; break; + } - fill_rect(x, y, w, h) { - self.postMessage({ type: 'canvas', data: [ 'fill_rect', x, y, w, h ] }); + if (method != "") { + self.postMessage({ type: 'canvas', data: [ 'text_align' , method ] }); + } + }, - let canvas_data = new Int32Array(canvas_data_buffer); - Atomics.wait(canvas_data, 2, 0); - Atomics.store(canvas_data, 2, 0); - } + // Drawing + arc(x, y, r, sa, ea, cc) { self.postMessage({ type: 'canvas', data: [ 'arc', x, y, r, sa, ea, cc != 0 ] }); }, + arc_to(x1, y1, x2, y2, r) { self.postMessage({ type: 'canvas', data: [ 'arc_to', x1, y1, x2, y2, r ] }); }, + begin_path() { self.postMessage({ type: 'canvas', data: [ 'begin_path' ] }); }, + clear() { self.postMessage({ type: 'canvas', data: [ 'clear' ] }); }, + clear_rect(x, y, w, h) { self.postMessage({ type: 'canvas', data: [ 'clear_rect', x, y, w, h ] }); }, + clip(cliprule) { + let method = ""; + switch (cliprule) { + case 1: method = "nonzero"; break; + case 2: method = "evenodd"; break; + } + + if (method != "") { + self.postMessage({ type: 'canvas', data: [ 'clip', method ] }); + } + }, + close_path() { self.postMessage({ type: 'canvas', data: [ 'close_path' ] }); }, + ellipse(x, y, rx, ry, r, sa, ea, cc) { self.postMessage({ type: 'canvas', data: [ 'ellipse', x, y, rx, ry, r, sa, ea, cc != 0 ] }); }, + fill(fillrule) { + let method = ""; + switch (fillrule) { + case 1: method = "nonzero"; break; + case 2: method = "evenodd"; break; + } + + if (method != "") { + self.postMessage({ type: 'canvas', data: [ 'fill', method ] }); + } + }, + fill_rect(x, y, w, h) { self.postMessage({ type: 'canvas', data: [ 'fill_rect', x, y, w, h ] }); }, + fill_text(textptr, textlen, x, y, max_width) { + self.postMessage({ type: 'canvas', data: [ 'fill_text', S(textptr, textlen), x, y, max_width > 0 ? max_width : null ] }); + }, + line_to(x, y) { self.postMessage({ type: 'canvas', data: [ 'line_to', x, y ] }); }, + move_to(x, y) { self.postMessage({ type: 'canvas', data: [ 'move_to', x, y ] }); }, + quadratic_curve_to(cpx, cpy, x, y) { self.postMessage({ type: 'canvas', data: [ 'quadratic_curve_to', cpx, cpy, x, y ] }); }, + rect(x, y, w, h) { self.postMessage({ type: 'canvas', data: [ 'rect', x, y, w, h ] }); }, + restore() { self.postMessage({ type: 'canvas', data: [ 'restore' ] }); }, + rotate(angle) { self.postMessage({ type: 'canvas', data: [ 'rotate', angle ] }); }, + save() { self.postMessage({ type: 'canvas', data: [ 'save' ] }); }, + scale(x, y) { self.postMessage({ type: 'canvas', data: [ 'scale', x, y ] }); }, + set_transform(a, b, c, d, e, f) { self.postMessage({ type: 'canvas', data: [ 'set_transform', a, b, c, d, e, f ] }); }, + stroke() { self.postMessage({ type: 'canvas', data: [ 'stroke' ] }); }, + stroke_rect(x, y, w, h) { self.postMessage({ type: 'canvas', data: [ 'stroke_rect', x, y, w, h ] }); }, + stroke_text(textptr, textlen, x, y, max_width) { self.postMessage({ type: 'canvas', data: [ 'stroke_text', S(textptr, textlen), x, y, max_width > 0 ? max_width : null ] }); }, + transform(a, b, c, d, e, f) { self.postMessage({ type: 'canvas', data: [ 'transform', a, b, c, d, e, f ] }); }, + translate(x, y) { self.postMessage({ type: 'canvas', data: [ 'translate', x, y ] }); }, } };