1const TEXTURE_SIZE = 512; 2const POISSON_ITERATIONS = 64; 3 4/** 5 * @param {Float32Array} alpha 6 * @param {number} size 7 */ 8function buildContourField(alpha, size) { 9 const pixelCount = size * size; 10 const shapeMask = new Uint8Array(pixelCount); 11 const interiorPixels = []; 12 const redPixels = []; 13 const blackPixels = []; 14 15 for (let i = 0; i < pixelCount; i++) { 16 shapeMask[i] = alpha[i] > 0 ? 1 : 0; 17 } 18 19 for (let y = 0; y < size; y++) { 20 for (let x = 0; x < size; x++) { 21 const idx = y * size + x; 22 if (!shapeMask[idx]) { 23 continue; 24 } 25 26 let isBoundary = x === 0 || x === size - 1 || y === 0 || y === size - 1; 27 for (let oy = -1; oy <= 1 && !isBoundary; oy++) { 28 for (let ox = -1; ox <= 1; ox++) { 29 if (ox === 0 && oy === 0) { 30 continue; 31 } 32 if (!shapeMask[(y + oy) * size + x + ox]) { 33 isBoundary = true; 34 break; 35 } 36 } 37 } 38 39 if (!isBoundary) { 40 interiorPixels.push(idx); 41 if ((x + y) % 2 === 0) { 42 redPixels.push(idx); 43 } else { 44 blackPixels.push(idx); 45 } 46 } 47 } 48 } 49 50 const values = new Float32Array(pixelCount); 51 const omega = 1.9; 52 const passes = [redPixels, blackPixels]; 53 54 for (let iter = 0; iter < POISSON_ITERATIONS; iter++) { 55 for (const pixels of passes) { 56 for (const idx of pixels) { 57 const east = idx + 1; 58 const west = idx - 1; 59 const north = idx - size; 60 const south = idx + size; 61 const sum = 62 (shapeMask[east] ? values[east] : 0) + 63 (shapeMask[west] ? values[west] : 0) + 64 (shapeMask[north] ? values[north] : 0) + 65 (shapeMask[south] ? values[south] : 0); 66 const next = (0.01 + sum) / 4; 67 values[idx] = omega * next + (1 - omega) * values[idx]; 68 } 69 } 70 } 71 72 const smoothed = new Float32Array(pixelCount); 73 for (let pass = 0; pass < 2; pass++) { 74 smoothed.fill(0); 75 for (const idx of interiorPixels) { 76 let sum = values[idx] * 4; 77 let weight = 4; 78 79 for (let oy = -1; oy <= 1; oy++) { 80 for (let ox = -1; ox <= 1; ox++) { 81 if (ox === 0 && oy === 0) { 82 continue; 83 } 84 85 const neighbor = idx + oy * size + ox; 86 if (!shapeMask[neighbor]) { 87 continue; 88 } 89 90 const neighborWeight = ox === 0 || oy === 0 ? 2 : 1; 91 sum += values[neighbor] * neighborWeight; 92 weight += neighborWeight; 93 } 94 } 95 96 smoothed[idx] = sum / weight; 97 } 98 values.set(smoothed); 99 } 100 101 let maxValue = 0; 102 for (const idx of interiorPixels) { 103 maxValue = Math.max(maxValue, values[idx]); 104 } 105 maxValue = maxValue || 1; 106 107 const contour = new Uint8Array(pixelCount); 108 for (let i = 0; i < pixelCount; i++) { 109 contour[i] = shapeMask[i] ? Math.round(255 * (1 - values[i] / maxValue)) : 255; 110 } 111 112 return contour; 113} 114 115/** 116 * @param {string} path 117 * @param {number} viewBoxSize 118 */ 119function getShapeTextureData(path, viewBoxSize) { 120 const canvas = new OffscreenCanvas(TEXTURE_SIZE, TEXTURE_SIZE); 121 const ctx = canvas.getContext('2d', { willReadFrequently: true }); 122 if (!ctx) { 123 return null; 124 } 125 126 const scale = TEXTURE_SIZE / viewBoxSize; 127 const shapePath = new Path2D(path); 128 129 // G channel: sharp opacity mask. 130 ctx.fillStyle = 'white'; 131 ctx.save(); 132 ctx.scale(scale, scale); 133 ctx.fill(shapePath); 134 ctx.restore(); 135 const sharp = ctx.getImageData(0, 0, TEXTURE_SIZE, TEXTURE_SIZE).data; 136 const sharpAlpha = new Float32Array(TEXTURE_SIZE * TEXTURE_SIZE); 137 for (let i = 0; i < TEXTURE_SIZE * TEXTURE_SIZE; i++) { 138 sharpAlpha[i] = sharp[i * 4 + 3]; 139 } 140 141 const contour = buildContourField(sharpAlpha, TEXTURE_SIZE); 142 143 // Rows are written bottom-up, replacing the UNPACK_FLIP_Y_WEBGL upload flag 144 // that is deprecated for typed-array uploads. 145 const data = new Uint8Array(TEXTURE_SIZE * TEXTURE_SIZE * 4); 146 for (let y = 0; y < TEXTURE_SIZE; y++) { 147 const sourceRow = TEXTURE_SIZE - 1 - y; 148 for (let x = 0; x < TEXTURE_SIZE; x++) { 149 const sourceIndex = sourceRow * TEXTURE_SIZE + x; 150 const destinationIndex = (y * TEXTURE_SIZE + x) * 4; 151 data[destinationIndex + 0] = contour[sourceIndex]; 152 data[destinationIndex + 1] = sharp[sourceIndex * 4 + 3]; 153 data[destinationIndex + 2] = 0; 154 data[destinationIndex + 3] = 255; 155 } 156 } 157 158 return data; 159} 160 161/** @param {MessageEvent<{ path: string;
161 viewBoxSize: number }>} event */ 162self.onmessage = (event) => { 163 const data = getShapeTextureData(event.data.path, event.data.viewBoxSize); 164 self.postMessage(data, { transfer: data ? [data.buffer] : [] }); 165};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.