-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
< img-victor > #3
Comments
In my old blog, the portfolio thumbnail is shown by an animate SVG path like it's drawn by hands. It looks nice and sophisticated. That's not new tech, just combining CSS So for the new site, I find another way to do this. Let's see some demos first: Set goals
The <img> can be drawn by a canvas and read the raw data by Task1:
The web component is well supported by browsers now(2022), it's simple to use and the shadow DOM will isolate styles for us. And it works well with popular libs like React or Vue, I think it would be a good choice for this. Task3:
I believe the translate function in Goal1 will be a CPU-bound one. So it would be better if there is a WASM version of it, so we could speed it up. Task4: We don't want the translation process to hang the page since it might take some time to finish. So it's better to put it in a web worker. Task5: OK, Let's build this step by step. Step1: Build a Web Component skeletonWe'll build a simple Web Component called <img-victor>, which simplely works like a <img>, but rendering the image with a canvas. const template = document.createElement("template");
template.innerHTML = `
<style>
:host {
display: inline-block;
}
</style>
<canvas id="canvas"></canvas>
`;
class ImageVictor extends HTMLElement {
static get observedAttributes() {
return ["src"];
}
static async loadImage(url) {
// TBD task1
}
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(
template.content.cloneNode(true)
);
}
async attributeChangedCallback(name, prev, next) {
if (prev === next) {
return;
}
switch (name) {
case "src": {
const img = await ImageVictor.loadImage(this.src);
this._render(img);
break;
}
default:
break;
}
}
_render(img) {
// TBD task2
}
get src() {
return this.getAttribute("src");
}
set src(val = "") {
this.setAttribute("src", val);
}
connectedCallback() {
if (!this.hasAttribute("src")) {
this.src = "";
}
}
disconnectedCallback() {}
}
// task3
window.customElements.define("img-victor", ImageVictor); The Web Component attaches the predefined template in the shadow DOM. It watches the 'src' attributes, fetching the image with a static function Now we implement the For the static async loadImage(url) {
return new Promise((resolve, reject) => {
let img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
try {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const { width, height } = img;
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
resolve(ctx.getImageData(0, 0, width, height));
} catch (error) {
reject(error);
}
};
img.onerror = (error) => reject(error);
img.src = url;
});
} For the _render(img) {
const canvas = this.shadowRoot.querySelector("#canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
ctx.putImageData(img, 0, 0);
} Step2: Translate ImageData to line vectorsThis is the hard part. The first idea that comes to my mind is that I need some kind of line detection algorithm. After some searching, Hough Transform and Canny edge detector pop out. But the Canny edge detector returns points instead of line segments, and the number of points is hudge when the image gets complicated. Joining those points into lines is a difficult job. Then I found this algorithm LSD: a Line Segment Detector . It's fast and the output looks promising, and there is even a C version implementation on the website. Compile C to WASMI compile the code with emscripten. It generates some glue codes in javascript and a separate wasm file. Notice that although emscripten proxy the C function to javascript for us ( Module['gray'] = ({ data, width, height }) => {
const arr = new Float64Array(width * height);
for (let i = 0; i < data.length; i += 4) {
arr[i/4] = data[i];
}
return arr;
};
Module['chunk'] = (input = [], size = 7) => {
const result = [];
const len = input.length;
for (let i = 0; i < len; i += size) {
result.push(input.slice(i, i + size));
}
return result;
};
Module['translate'] = ({ data, width, height }) => {
let lines;
let resultPtr;
let countPtr = Module._malloc(8);
const dataPtr = Module._malloc(width * height * 8);
const grayData = Module.gray({ data, width, height});
try {
Module.HEAPF64.set(grayData, dataPtr / 8);
resultPtr = Module._lsd(countPtr, dataPtr, width, height);
const len = Module.getValue(countPtr, 'i32');
lines = Module.chunk(
Module.HEAPF64.subarray(resultPtr / 8, resultPtr / 8 + len * 7),
);
} finally {
Module._free(dataPtr);
Module._free(countPtr);
if(resultPtr) {
Module._free(resultPtr);
}
}
return lines;
}; Once we have the bridge ready, compile the wasm in CLI, and specify the glue output as ESM.
Put it all together, now we have the import { ImageVictor } from "/js/component.js";
import Module from "/js/lsd.mjs";
// The default export of lsd.mjs is a function returns a Promise,
// which will be resolved to an object with all the bridge functions.
const LSD = await Module();
const img = await ImageVictor.loadImage("/img/senbazuru.png");
const lines = LSD.translate(img);
// lines: [start, end] [[x1, y1], [x2, y2]]
[
{
"0": 168.9860607982964, // x1
"1": 180.61778577533605, // y1
"2": 169.45004281672658, // x2
"3": 155.6032063493381, // y2
"4": 3.9579894109323166,
"5": 0.125,
"6": 35.104631104903746
},
....
] (Notice: The LSD in C only takes gray data of the image, it can be done by applying a Step3: Rewrite the
|
The text was updated successfully, but these errors were encountered: