// wfc_setup_worker.js let sampleWidth, sampleHeight, srcDataView, currentPatternSizeW, enableRotationsW; let patterns, patternByID, patternByIndex, patternIdToIndex; let adjacency; // --- Helper functions (subset of original, or new for worker) --- function Uint8ArrayToHexString(u8a) { let hex = ''; for (let i = 0; i < u8a.length; i++) { let h = u8a[i].toString(16); if (h.length < 2) h = '0' + h; hex += h; } return hex; } function rotatePatternCells(flatCells, pSize, angle) { const newFlatCells = new Uint8Array(pSize * pSize * 4); if (angle === 0) { newFlatCells.set(flatCells); return newFlatCells; } for (let r_orig = 0; r_orig < pSize; r_orig++) { for (let c_orig = 0; c_orig < pSize; c_orig++) { const oldIdxBase = (r_orig * pSize + c_orig) * 4; let nr, nc; if (angle === 90) { nr = c_orig; nc = pSize - 1 - r_orig; } else if (angle === 180) { nr = pSize - 1 - r_orig; nc = pSize - 1 - c_orig; } else if (angle === 270) { nr = pSize - 1 - c_orig; nc = r_orig; } else { newFlatCells.set(flatCells); return newFlatCells; } // Should not happen with defined angles const newIdxBase = (nr * pSize + nc) * 4; newFlatCells[newIdxBase] = flatCells[oldIdxBase]; newFlatCells[newIdxBase + 1] = flatCells[oldIdxBase + 1]; newFlatCells[newIdxBase + 2] = flatCells[oldIdxBase + 2]; newFlatCells[newIdxBase + 3] = flatCells[oldIdxBase + 3]; } } return newFlatCells; } // --- Stats helpers (subset for this worker) --- function estimateStringBytes(str) { return str ? str.length * 2 : 0; } function estimateObjectOverhead(obj) { if (!obj) return 0; return ( Object.keys(obj).length * (estimateStringBytes('key_placeholder_avg') + 8 + 4) + 16 ); } let workerStats = { patternExtraction: {}, adjacency: {}, memory: {}, }; function extractPatterns() { console.log( '[Setup Worker] extractPatterns started. sampleWidth:', sampleWidth, 'sampleHeight:', sampleHeight, 'pSize:', currentPatternSizeW ); // Log a small portion of srcDataView to check its general content if (srcDataView && srcDataView.length > 0) { console.log( '[Setup Worker] srcDataView first 100 bytes:', srcDataView.slice(0, 100) ); } else { console.error( '[Setup Worker] srcDataView is null, empty, or undefined at start of extractPatterns!' ); // If srcDataView is bad, we can't proceed meaningfully. throw new Error('srcDataView is invalid in extractPatterns'); } const t0 = performance.now(); const countMap = new Map(); const keyToFlatCells = new Map(); const pSize = currentPatternSizeW; let rawPatternCounter = 0; for (let y = 0; y <= sampleHeight - pSize; y++) { for (let x = 0; x <= sampleWidth - pSize; x++) { rawPatternCounter++; const originalFlatBlock = new Uint8Array(pSize * pSize * 4); let k = 0; for (let dy = 0; dy < pSize; dy++) { for (let dx = 0; dx < pSize; dx++) { const imgPixelY = y + dy; const imgPixelX = x + dx; const srcIdx = (imgPixelY * sampleWidth + imgPixelX) * 4; if (srcIdx + 3 >= srcDataView.length) { // Boundary check console.error( `[Setup Worker] Out of bounds access attempt: srcIdx=${srcIdx}, y=${y},x=${x}, dy=${dy},dx=${dx}, imgPixelY=${imgPixelY},imgPixelX=${imgPixelX}, sampleWidth=${sampleWidth}, sampleHeight=${sampleHeight}, srcDataView.length=${srcDataView.length}` ); // Fill with a debug color or handle error originalFlatBlock[k++] = 255; originalFlatBlock[k++] = 0; originalFlatBlock[k++] = 0; originalFlatBlock[k++] = 255; // Red continue; // Skip this pixel } originalFlatBlock[k++] = srcDataView[srcIdx]; originalFlatBlock[k++] = srcDataView[srcIdx + 1]; originalFlatBlock[k++] = srcDataView[srcIdx + 2]; originalFlatBlock[k++] = srcDataView[srcIdx + 3]; } } if (x === 0 && y === 0) { // Log only the very first extracted block console.log( '[Setup Worker] First originalFlatBlock (x=0, y=0, pSize=' + pSize + '):' ); let pixelsToLog = []; for (let i = 0; i < originalFlatBlock.length; i += 4) { pixelsToLog.push( `(${originalFlatBlock[i]},${originalFlatBlock[i + 1]},${ originalFlatBlock[i + 2] },${originalFlatBlock[i + 3]})` ); } console.log(pixelsToLog.join(' ')); } const rotationsToConsider = enableRotationsW ? [0, 90, 180, 270] : [0]; for (const angle of rotationsToConsider) { const currentFlatBlockCells = rotatePatternCells( originalFlatBlock, pSize, angle ); const key = Uint8ArrayToHexString(currentFlatBlockCells); countMap.set(key, (countMap.get(key) || 0) + 1); if (!keyToFlatCells.has(key)) { keyToFlatCells.set(key, currentFlatBlockCells); } } } } workerStats.patternExtraction.rawPatternCount = rawPatternCounter * (enableRotationsW ? 4 : 1); patterns = []; patternByID = {}; patternByIndex = []; // This will be an array of pattern objects patternIdToIndex = {}; let idx = 0; for (const [key, freq] of countMap.entries()) { const patID = 'p' + idx; const flatCellsData = keyToFlatCells.get(key); // This is a Uint8Array const patObj = { id: patID, cells: flatCellsData, frequency: freq, index: idx, }; patterns.push(patObj); patternByID[patID] = patObj; patternByIndex[idx] = patObj; // Store by index patternIdToIndex[patID] = idx; idx++; } workerStats.patternExtraction.timeMs = performance.now() - t0; workerStats.patternExtraction.uniquePatternCount = patterns.length; workerStats.patternExtraction.rotationsEnabled = enableRotationsW; workerStats.memory.patternCellsBytes = 0; workerStats.memory.patternsArrayBytes = 0; patterns.forEach((p) => { workerStats.memory.patternCellsBytes += p.cells.byteLength; workerStats.memory.patternsArrayBytes += estimateStringBytes(p.id) + 4 + 4 + 8 + 16; }); workerStats.memory.patternsArrayBytes += 16; workerStats.memory.patternByIdBytes = estimateObjectOverhead(patternByID); Object.keys(patternByID).forEach((key) => { workerStats.memory.patternByIdBytes += estimateStringBytes(key) + 8; }); self.postMessage({ type: 'SETUP_PROGRESS', payload: { statusMessage: `Found ${patterns.length} unique patterns. Building adjacency...`, statsUpdate: { patternExtraction: workerStats.patternExtraction, memory: workerStats.memory, }, }, }); } // --- CORRECTED `buildAdjacency` for the OVERLAPPING MODEL --- // --- Paste this into wfc_setup_worker.js --- function buildAdjacency() { const t1 = performance.now(); adjacency = {}; if (!patterns) return; const pSize = currentPatternSizeW; let ruleCount = 0; for (const pat of patterns) { adjacency[pat.id] = { up: [], down: [], left: [], right: [] }; } for (const p of patterns) { for (const q of patterns) { if (p.id === q.id && patterns.length === 1) { adjacency[p.id].right.push(q.index); ruleCount++; adjacency[q.id].left.push(p.index); ruleCount++; adjacency[p.id].down.push(q.index); ruleCount++; adjacency[q.id].up.push(p.index); ruleCount++; continue; } // Check if q can be to the RIGHT of p (OVERLAPPING MODEL) // The right N-1 columns of p must match the left N-1 columns of q. let canSitRight = true; if (pSize > 1) { for (let r = 0; r < pSize; r++) { // For each row for (let c = 0; c < pSize - 1; c++) { // For each overlapping column for (let ch = 0; ch < 4; ch++) { // Compare p's pixel at [r][c+1] with q's pixel at [r][c] const pPixel = p.cells[(r * pSize + (c + 1)) * 4 + ch]; const qPixel = q.cells[(r * pSize + c) * 4 + ch]; if (pPixel !== qPixel) { canSitRight = false; break; } } if (!canSitRight) break; } if (!canSitRight) break; } } else { canSitRight = true; // 1x1 patterns are always adjacent } if (canSitRight) { adjacency[p.id].right.push(q.index); ruleCount++; adjacency[q.id].left.push(p.index); ruleCount++; } // Check if q can be DOWN from p (OVERLAPPING MODEL) // The bottom N-1 rows of p must match the top N-1 rows of q. let canSitDown = true; if (pSize > 1) { for (let r = 0; r < pSize - 1; r++) { // For each overlapping row for (let c = 0; c < pSize; c++) { // For each column in that row for (let ch = 0; ch < 4; ch++) { // Compare p's pixel at [r+1][c] with q's pixel at [r][c] const pPixel = p.cells[((r + 1) * pSize + c) * 4 + ch]; const qPixel = q.cells[(r * pSize + c) * 4 + ch]; if (pPixel !== qPixel) { canSitDown = false; break; } } if (!canSitDown) break; } if (!canSitDown) break; } } else { canSitDown = true; // 1x1 patterns are always adjacent } if (canSitDown) { adjacency[p.id].down.push(q.index); ruleCount++; adjacency[q.id].up.push(p.index); ruleCount++; } } } workerStats.adjacency.totalRules = ruleCount; workerStats.adjacency.timeMs = performance.now() - t1; let adjMem = estimateObjectOverhead(adjacency); if (adjacency) { for (const patId in adjacency) { adjMem += estimateStringBytes(patId); adjMem += estimateObjectOverhead(adjacency[patId]); ['up', 'down', 'left', 'right'].forEach((dir) => { adjMem += estimateStringBytes(dir); adjMem += adjacency[patId][dir].length * (patterns.length > 65535 ? 4 : 2) + 16; }); } } workerStats.memory.adjacencyBytes = adjMem; } self.onmessage = function (e) { const { type, imageDataBuffer, width, height, patternSize, enableRotations } = e.data; if (type === 'START_SETUP') { console.log( '[Setup Worker] Received START_SETUP. Image data buffer length:', imageDataBuffer ? imageDataBuffer.byteLength : 'N/A' ); sampleWidth = width; sampleHeight = height; srcDataView = new Uint8ClampedArray(imageDataBuffer); currentPatternSizeW = patternSize; enableRotationsW = enableRotations; try { extractPatterns(); if (!patterns || patterns.length === 0) { self.postMessage({ type: 'SETUP_COMPLETE', error: 'No patterns extracted.', }); // Send error within SETUP_COMPLETE for main thread to handle return; } buildAdjacency(); // patterns[i].cells are Uint8Arrays. Their .buffer will be transferred. const transferablePatternCellBuffers = patterns.map( (p) => p.cells.buffer ); self.postMessage( { type: 'SETUP_COMPLETE', payload: { patternsData: { patterns: patterns, // Array of pattern objects, .cells are Uint8Array patternByID: patternByID, patternByIndex: patternByIndex, // Send this explicitly if main thread uses it by this name patternIdToIndex: patternIdToIndex, adjacency: adjacency, }, stats: workerStats, }, }, transferablePatternCellBuffers ); } catch (err) { console.error('[Setup Worker] Error during setup process:', err); self.postMessage({ type: 'SETUP_COMPLETE', error: err.message + (err.stack ? '\n' + err.stack : ''), }); } } }; // Catch unhandled errors in the worker self.onerror = function (message, source, lineno, colno, error) { console.error( '[Setup Worker] Unhandled error:', message, source, lineno, colno, error ); // Try to inform the main thread, though if postMessage itself fails, this might not work. try { self.postMessage({ type: 'SETUP_COMPLETE', error: `Unhandled error in setup worker: ${message}`, }); } catch (e) { console.error( '[Setup Worker] Failed to postMessage about unhandled error.', e ); } return true; // Prevents default error handling if possible };