Skip to content

Commit 8f5cd5e

Browse files
Handle errors
1 parent fed933e commit 8f5cd5e

File tree

2 files changed

+94
-7
lines changed

2 files changed

+94
-7
lines changed

public/app.js

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@ const METHODES = {
1616
grand_rhombitrihex: 9, hex_tronque: 10,
1717
};
1818

19+
const EXPORTS_CODES_METHODES = {
20+
hex: "methode_hexagone",
21+
square: "methode_carre",
22+
triangle: "methode_triangle",
23+
trihex: "methode_trihex",
24+
snub_trihex: "methode_snub_trihex",
25+
triangulaire_elongue: "methode_triangulaire_elongue",
26+
carre_snub: "methode_carre_snub",
27+
rhombitrihex: "methode_rhombitrihex",
28+
carre_tronque: "methode_carre_tronque",
29+
grand_rhombitrihex: "methode_grand_rhombitrihex",
30+
hex_tronque: "methode_hex_tronque",
31+
};
32+
1933
let wasm = null;
2034
let loadedImage = null;
2135
let renderToken = 0;
@@ -43,6 +57,7 @@ async function chargerWasm() {
4357
}
4458

4559
wasm = instance.exports;
60+
synchroniserCodesMethodes(wasm);
4661
status.textContent = "Moteur WASM charge.";
4762
btnApply.disabled = false;
4863
document.getElementById("upload-zone").style.pointerEvents = "auto";
@@ -88,6 +103,14 @@ function km_ajouter(r, g, b) { wasm.km_ajouter(r, g, b); }
88103
function km_calculer(maxIter) { wasm.km_calculer(maxIter); }
89104
function km_resultat() { return [Number(wasm.km_r()), Number(wasm.km_g()), Number(wasm.km_b())]; }
90105

106+
function synchroniserCodesMethodes(exports) {
107+
for (const [nom, exportNom] of Object.entries(EXPORTS_CODES_METHODES)) {
108+
if (typeof exports[exportNom] !== "function") continue;
109+
const code = Number(exports[exportNom]());
110+
if (Number.isInteger(code) && code >= 0) METHODES[nom] = code;
111+
}
112+
}
113+
91114
function boitePolygone(sommets) {
92115
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
93116
for (const [x, y] of sommets) {
@@ -178,6 +201,28 @@ function dimensionsScalees(imgEl) {
178201
return [w, h];
179202
}
180203

204+
function lireSommetsTuile(ti) {
205+
const n = Number(wasm.tuile_n_sommets(ti));
206+
if (!Number.isInteger(n) || n < 3 || n > 64) return null;
207+
const sommets = [];
208+
for (let j = 0; j < n; j++) {
209+
const x = Number(wasm.tuile_sommet_x(ti, j));
210+
const y = Number(wasm.tuile_sommet_y(ti, j));
211+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
212+
sommets.push([x, y]);
213+
}
214+
return sommets;
215+
}
216+
217+
function lireNombreSommetsTuile(ti) {
218+
try {
219+
const n = Number(wasm.tuile_n_sommets(ti));
220+
return Number.isInteger(n) && n >= 0 ? n : Number.MAX_SAFE_INTEGER;
221+
} catch (err) {
222+
return Number.MAX_SAFE_INTEGER;
223+
}
224+
}
225+
181226
function entierSecurise(valeur, secours, minimum = null, maximum = null) {
182227
let n = Number.isFinite(valeur) ? Math.trunc(valeur) : Math.trunc(Number(valeur));
183228
if (!Number.isFinite(n)) n = secours;
@@ -218,24 +263,38 @@ async function rendreSortie() {
218263
ctx.fillStyle = `rgb(${bgCouleur[0]},${bgCouleur[1]},${bgCouleur[2]})`;
219264
ctx.fillRect(0, 0, w, h);
220265

221-
const code = entierSecurise(METHODES[state.method], 0, 0);
266+
const code = entierSecurise(METHODES[state.method], METHODES.hex, 0);
222267
const cote = entierSecurise(state.side, 30, 1);
223268
if (cote !== state.side) state.side = cote;
224269
const nTuiles = Number(wasm.generer_tuiles(w, h, cote, code));
270+
if (!Number.isInteger(nTuiles) || nTuiles < 0) {
271+
throw new Error(`Nombre de tuiles invalide renvoye par WASM: ${nTuiles}`);
272+
}
225273
const indices = Array.from({ length: nTuiles }, (_, i) => i);
226-
indices.sort((a, b) => Number(wasm.tuile_n_sommets(a)) - Number(wasm.tuile_n_sommets(b)));
274+
indices.sort((a, b) => lireNombreSommetsTuile(a) - lireNombreSommetsTuile(b));
227275

228276
const cssContour = couleurContourCss();
277+
let tuilesInvalides = 0;
229278
for (const ti of indices) {
230-
const n = Number(wasm.tuile_n_sommets(ti));
231-
const sommets = [];
232-
for (let j = 0; j < n; j++)
233-
sommets.push([Number(wasm.tuile_sommet_x(ti, j)), Number(wasm.tuile_sommet_y(ti, j))]);
279+
let sommets = null;
280+
try {
281+
sommets = lireSommetsTuile(ti);
282+
} catch (err) {
283+
tuilesInvalides++;
284+
console.warn(`[pixel2polygon] Tuile ${ti} ignoree :`, err);
285+
continue;
286+
}
287+
if (!sommets) {
288+
tuilesInvalides++;
289+
continue;
290+
}
234291
const couleur = couleurTuile(pixelData, w, h, sommets);
235292
if (couleur) dessinerTuile(ctx, sommets, couleur, state.outlineWidth, cssContour);
236293
}
237294

238-
status.textContent = `Rendu termine : ${nTuiles} tuiles pour le mode ${state.method}.`;
295+
status.textContent = tuilesInvalides > 0
296+
? `Rendu termine : ${nTuiles - tuilesInvalides}/${nTuiles} tuiles valides pour le mode ${state.method}.`
297+
: `Rendu termine : ${nTuiles} tuiles pour le mode ${state.method}.`;
239298
btnDl.disabled = false;
240299
} catch (err) {
241300
console.error("[pixel2polygon] Echec du rendu :", err);

tests/smoke.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,33 @@ async function testRenderSanitizesTileSizeBeforeWasm() {
363363
assert.strictEqual(api.state.side, 30);
364364
}
365365

366+
async function testRenderSkipsInvalidTilesWithoutCrashing() {
367+
const { elements, api } = buildHarness();
368+
const sourceCanvas = elements.get("source-canvas");
369+
const status = elements.get("wasm-status");
370+
const download = elements.get("btn-download");
371+
sourceCanvas.width = 10;
372+
sourceCanvas.height = 10;
373+
374+
api.state.method = "hex";
375+
api.setWasm({
376+
couleur_moyenne(total, count) { return Math.round(total / count); },
377+
km_init() {}, km_ajouter() {}, km_calculer() {},
378+
km_r() { return 0; }, km_g() { return 0; }, km_b() { return 0; },
379+
generer_tuiles() { return 2; },
380+
tuile_n_sommets(i) {
381+
if (i === 0) return 4;
382+
throw new Error("index out of bounds");
383+
},
384+
tuile_sommet_x(i, j) { return [0, 10, 10, 0][j]; },
385+
tuile_sommet_y(i, j) { return [0, 0, 10, 10][j]; },
386+
});
387+
388+
await api.rendreSortie();
389+
assert.match(status.textContent, /1\/2 tuiles valides/);
390+
assert.strictEqual(download.disabled, false);
391+
}
392+
366393
function testKMeansSamplingStaysUnderWasmLimit() {
367394
const { api } = buildHarness();
368395
let sampleCount = 0;
@@ -389,6 +416,7 @@ async function run() {
389416
testKMeansSamplingStaysUnderWasmLimit();
390417
await testFlowerImageProducesTilesInWasm();
391418
await testRenderSanitizesTileSizeBeforeWasm();
419+
await testRenderSkipsInvalidTilesWithoutCrashing();
392420
console.log("Smoke tests passed.");
393421
}
394422

0 commit comments

Comments
 (0)