|
14 | 14 | const KIND_NORMAL = 0, KIND_GLAZE = 1, KIND_SCUMBLE = 2, KIND_IMPASTO = 3, KIND_FILL = 4; |
15 | 15 | const clamp = (v, a, b) => (v < a ? a : v > b ? b : v); |
16 | 16 |
|
| 17 | + /* 安全批量追加。 |
| 18 | + * 不能用 `arr.push(...big)`:笔触数量多时(素描把细腻程度拉满会到十几万条) |
| 19 | + * 会超出引擎对展开参数的限制,抛 "Maximum call stack size exceeded"。 |
| 20 | + * 分块追加不受这个限制。 */ |
| 21 | + function appendAll(dst, src) { |
| 22 | + const CH = 4096; |
| 23 | + for (let i = 0; i < src.length; i += CH) { |
| 24 | + const chunk = src.slice(i, i + CH); |
| 25 | + for (let j = 0; j < chunk.length; j++) dst.push(chunk[j]); |
| 26 | + } |
| 27 | + } |
| 28 | + |
17 | 29 | /* ---------------------------------------------------------------- 高斯 */ |
18 | 30 | function gaussKernel(sigma) { |
19 | 31 | const r = Math.max(1, Math.ceil(sigma * 3)); |
|
754 | 766 | const stages = []; |
755 | 767 | const WT = {}, LB = {}; |
756 | 768 | OIL_STAGE.forEach(([n, l, w]) => { WT[n] = w; LB[n] = l; }); |
| 769 | + /* 笔触预算:细腻程度拉满时笔触会到 20 万条 / 22MB,手机端扛不住。 |
| 770 | + * 按工序权重等比稀释,保持「大笔在前、细笔在后」的比例关系。 */ |
| 771 | + const OIL_MAX = 130000; |
| 772 | + const oilW = OIL_STAGE.reduce((a, x) => a + x[2], 0); |
757 | 773 | const push = (name, ss) => { |
758 | | - stages.push({ name, label: LB[name] || name, weight: WT[name] || 5, strokes: ss }); |
759 | | - if (onStage) onStage(name, ss.length, LB[name] || name); |
| 774 | + const allow = Math.max(600, Math.round(OIL_MAX * (WT[name] || 5) / oilW)); |
| 775 | + let kept = ss; |
| 776 | + if (ss.length > allow) { |
| 777 | + const stride = ss.length / allow; |
| 778 | + kept = []; |
| 779 | + for (let i = 0; i < allow; i++) kept.push(ss[Math.min(ss.length - 1, Math.floor(i * stride))]); |
| 780 | + } |
| 781 | + stages.push({ name, label: LB[name] || name, weight: WT[name] || 5, strokes: kept }); |
| 782 | + if (onStage) onStage(name, kept.length, LB[name] || name); |
760 | 783 | }; |
761 | 784 |
|
762 | 785 | const GB = groundColor(P, W, H, { mode: 'warm', darkFrac: 0.28 }); |
|
816 | 839 | Wd.push((w0 + P.rnd() * (w1 - w0)) * s); |
817 | 840 | Lay.push(P.sampleA(A.labels, g.X[i], g.Y[i])); |
818 | 841 | } |
819 | | - all.push(...P.make(g.X, g.Y, L, Wd, Lay, 5, 0, KIND_NORMAL, 0.8, 0.72, [70, 58, 46], 0.34, 'gris')); |
| 842 | + appendAll(all, P.make(g.X, g.Y, L, Wd, Lay, 5, 0, KIND_NORMAL, 0.8, 0.72, [70, 58, 46], 0.34, 'gris')); |
820 | 843 | } |
821 | 844 | push('grisaille', all); |
822 | 845 | } |
|
833 | 856 | Wd.push((w0 + P.rnd() * (w1 - w0)) * s); |
834 | 857 | Lay.push(P.sampleA(A.labels, g.X[i], g.Y[i])); |
835 | 858 | } |
836 | | - all.push(...P.make(g.X, g.Y, L, Wd, Lay, 5, 0, KIND_NORMAL, 1.0, 0.04, null, 0, 'block')); |
| 859 | + appendAll(all, P.make(g.X, g.Y, L, Wd, Lay, 5, 0, KIND_NORMAL, 1.0, 0.04, null, 0, 'block')); |
837 | 860 | } |
838 | 861 | push('block-in', all); |
839 | 862 | } |
|
852 | 875 | Wd.push((w0 + P.rnd() * (w1 - w0)) * s); |
853 | 876 | Lay.push(P.sampleA(A.labels, g.X[i], g.Y[i])); |
854 | 877 | } |
855 | | - all.push(...P.make(X, Y, L, Wd, Lay, 5, 0, KIND_NORMAL, 1.0, 0, null, 0, 'mass')); |
| 878 | + appendAll(all, P.make(X, Y, L, Wd, Lay, 5, 0, KIND_NORMAL, 1.0, 0, null, 0, 'mass')); |
856 | 879 | } |
857 | 880 | push('massing', all); |
858 | 881 | } |
|
871 | 894 | Wd.push((w0 + P.rnd() * (w1 - w0)) * s); |
872 | 895 | Lay.push(P.sampleA(A.labels, g.X[i], g.Y[i])); |
873 | 896 | } |
874 | | - all.push(...P.make(X, Y, L, Wd, Lay, 5, 0, KIND_NORMAL, 0.94, 0, null, 0, 'mod')); |
| 897 | + appendAll(all, P.make(X, Y, L, Wd, Lay, 5, 0, KIND_NORMAL, 0.94, 0, null, 0, 'mod')); |
875 | 898 | } |
876 | 899 | push('modelling', all); |
877 | 900 | } |
|
937 | 960 | Wd.push((0.85 + P.rnd() * 0.85) * s); |
938 | 961 | Lay.push(P.sampleA(A.labels, g.X[i], g.Y[i])); |
939 | 962 | } |
940 | | - det.push(...P.make(X, Y, L, Wd, Lay, 4, 1, KIND_NORMAL, 1.0, 0, null, 0, 'det')); |
| 963 | + appendAll(det, P.make(X, Y, L, Wd, Lay, 4, 1, KIND_NORMAL, 1.0, 0, null, 0, 'det')); |
941 | 964 | } |
942 | 965 | for (const r of rois) { |
943 | 966 | const g = P.grid(5.6 * s, 0.5, [r[0] + 1.7 * s, r[1] + 1.7 * s, r[2], r[3]]); |
|
950 | 973 | Wd.push((0.9 + P.rnd() * 0.9) * s); |
951 | 974 | Lay.push(P.sampleA(A.labels, g.X[i], g.Y[i])); |
952 | 975 | } |
953 | | - det.push(...P.make(X, Y, L, Wd, Lay, 4, 1, KIND_NORMAL, 1.0, 0, null, 0, 'det2')); |
| 976 | + appendAll(det, P.make(X, Y, L, Wd, Lay, 4, 1, KIND_NORMAL, 1.0, 0, null, 0, 'det2')); |
954 | 977 | } |
955 | 978 | for (const r of rois.slice(0, 5)) { |
956 | 979 | const e = P.edgeSeeds(r, 0.5, 2.6 * s); |
|
961 | 984 | Wd.push((0.8 + P.rnd() * 0.6) * s); |
962 | 985 | Lay.push(P.sampleA(A.labels, e.X[i], e.Y[i])); |
963 | 986 | } |
964 | | - det.push(...P.make(e.X, e.Y, L, Wd, Lay, 3, 1, KIND_NORMAL, 1.0, 0, null, 0, 'micro')); |
| 987 | + appendAll(det, P.make(e.X, e.Y, L, Wd, Lay, 3, 1, KIND_NORMAL, 1.0, 0, null, 0, 'micro')); |
965 | 988 | } |
966 | 989 | push('detail', det); |
967 | 990 | } |
|
1013 | 1036 | const stages = []; |
1014 | 1037 | const WT = {}, LB = {}; |
1015 | 1038 | LINE_STAGE.forEach(([n, l, w]) => { WT[n] = w; LB[n] = l; }); |
| 1039 | + /* 与油画同样的笔触预算,防止细腻程度拉满时数据量过大 */ |
| 1040 | + const LINE_MAX = 100000; |
| 1041 | + const lineW = LINE_STAGE.reduce((a, x) => a + x[2], 0); |
1016 | 1042 | const push = (name, ss) => { |
1017 | | - stages.push({ name, label: LB[name] || name, weight: WT[name] || 5, strokes: ss }); |
1018 | | - if (onStage) onStage(name, ss.length, LB[name] || name); |
| 1043 | + const allow = Math.max(500, Math.round(LINE_MAX * (WT[name] || 5) / lineW)); |
| 1044 | + let kept = ss; |
| 1045 | + if (ss.length > allow) { |
| 1046 | + const stride = ss.length / allow; |
| 1047 | + kept = []; |
| 1048 | + for (let i = 0; i < allow; i++) kept.push(ss[Math.min(ss.length - 1, Math.floor(i * stride))]); |
| 1049 | + } |
| 1050 | + stages.push({ name, label: LB[name] || name, weight: WT[name] || 5, strokes: kept }); |
| 1051 | + if (onStage) onStage(name, kept.length, LB[name] || name); |
1019 | 1052 | }; |
1020 | 1053 |
|
1021 | 1054 | /* 线色:冷白。原图越暗线越亮,越亮线越柔 */ |
|
1088 | 1121 | Wd.push(lw(0.78)); |
1089 | 1122 | Lay.push(P.sampleA(A.labels, e.X[i], e.Y[i])); |
1090 | 1123 | } |
1091 | | - all.push(...P.makeRaw(e.X, e.Y, L, Wd, Lay, 5, 1, KIND_NORMAL, 0.78, |
| 1124 | + appendAll(all, P.makeRaw(e.X, e.Y, L, Wd, Lay, 5, 1, KIND_NORMAL, 0.78, |
1092 | 1125 | Q => inkHex(P.lum(Q[0], Q[1])), 'line-fine', |
1093 | 1126 | Object.assign({}, EDGE_OPT, { edgeField: A.eFine }))); |
1094 | 1127 | } |
|
1408 | 1441 | const stages = []; |
1409 | 1442 | const WT = {}, LB = {}; |
1410 | 1443 | PENCIL_STAGE.forEach(([n, l, w]) => { WT[n] = w; LB[n] = l; }); |
| 1444 | + |
| 1445 | + /* 笔触预算:排线是网格播种,细腻程度拉满时数量会爆炸(约 20 万条 / 21MB), |
| 1446 | + * 手机端容易卡顿甚至 OOM。这里按总预算等比稀释,而不是砍掉某个阶段, |
| 1447 | + * 各阶段之间的比例关系保持不变。 */ |
| 1448 | + const MAX_STROKES = 90000; |
| 1449 | + const totalWeight = PENCIL_STAGE.reduce((a, x) => a + x[2], 0); |
1411 | 1450 | const push = (name, ss) => { |
1412 | | - stages.push({ name, label: LB[name] || name, weight: WT[name] || 5, strokes: ss }); |
1413 | | - if (onStage) onStage(name, ss.length, LB[name] || name); |
| 1451 | + const share = (WT[name] || 5) / totalWeight; |
| 1452 | + const allow = Math.max(300, Math.round(MAX_STROKES * share)); |
| 1453 | + let kept = ss; |
| 1454 | + if (ss.length > allow) { |
| 1455 | + const stride = ss.length / allow; |
| 1456 | + kept = []; |
| 1457 | + for (let i = 0; i < allow; i++) kept.push(ss[Math.min(ss.length - 1, Math.floor(i * stride))]); |
| 1458 | + } |
| 1459 | + stages.push({ name, label: LB[name] || name, weight: WT[name] || 5, strokes: kept }); |
| 1460 | + if (onStage) onStage(name, kept.length, LB[name] || name); |
1414 | 1461 | }; |
1415 | 1462 |
|
1416 | 1463 | /* 石墨色:原图越暗越黑。纸张很亮,调子从 ~208 往下压 */ |
|
1450 | 1497 | Wd.push((w0 + P.rnd() * (w1 - w0)) * s); |
1451 | 1498 | Lay.push(P.sampleA(A.labels, g.X[i], g.Y[i])); |
1452 | 1499 | } |
1453 | | - all.push(...P.makeRaw(X, Y, L, Wd, Lay, 6, 0, KIND_NORMAL, a, |
| 1500 | + appendAll(all, P.makeRaw(X, Y, L, Wd, Lay, 6, 0, KIND_NORMAL, a, |
1454 | 1501 | Q => graphite(P.lum(Q[0], Q[1]), dep), 'pc-tone', { maxTurn: 0.22 })); |
1455 | 1502 | } |
1456 | 1503 | push('tone', all); |
|
1471 | 1518 | Wd.push((wd + P.rnd() * 0.45) * Math.max(0.85, s)); |
1472 | 1519 | Lay.push(P.sampleA(A.labels, g.X[i], g.Y[i])); |
1473 | 1520 | } |
1474 | | - all.push(...P.makeRaw(X, Y, L, Wd, Lay, 4, 0, KIND_NORMAL, a, |
| 1521 | + appendAll(all, P.makeRaw(X, Y, L, Wd, Lay, 4, 0, KIND_NORMAL, a, |
1475 | 1522 | Q => graphite(P.lum(Q[0], Q[1]), 0.9), 'pc-hatch', { maxTurn: 0.16 })); |
1476 | 1523 | } |
1477 | 1524 | push('hatch', all); |
|
0 commit comments