// Grid-based panel detection (fast, lightweight) // Keep as final fallback export interface Panel { id: string; x: number; y: number; width: number; height: number; reading_order: number; } interface GridConfig { rows: number; cols: number; } function detectPanelsGrid( imageData: ImageData, config: GridConfig = { rows: 3, cols: 3 }, ): Panel[] { const panels: Panel[] = []; const cellWidth = imageData.width / config.cols; const cellHeight = imageData.height / config.rows; for (let y = 0; y < config.rows; y++) { for (let x = 0; x < config.cols; x++) { const cell = extractCell(imageData, x, y, cellWidth, cellHeight); if (!isEmpty(cell)) { panels.push({ id: `panel-${panels.length}`, x: (x / config.cols) * 100, y: (y / config.rows) * 100, width: (1 / config.cols) * 100, height: (1 / config.rows) * 100, reading_order: panels.length, }); } } } return mergeAdjacentPanels(panels); } function isEmpty(cellData: ImageData): boolean { // Simple edge detection to find empty space // Count white/transparent pixels let emptyPixels = 0; const totalPixels = cellData.width * cellData.height; const threshold = 0.95; // 95% empty = empty cell for (let i = 0; i < cellData.data.length; i += 4) { const r = cellData.data[i]; const g = cellData.data[i + 1]; const b = cellData.data[i + 2]; const a = cellData.data[i + 3]; // Consider white or transparent as empty if (a < 10 || (r > 250 && g > 250 && b > 250)) { emptyPixels++; } } return emptyPixels / totalPixels > threshold; } function mergeAdjacentPanels(panels: Panel[]): Panel[] { // Merge panels that are next to each other // Simplified algorithm - can be enhanced const merged: Panel[] = []; const used = new Set(); for (let i = 0; i < panels.length; i++) { if (used.has(i)) continue; let current = { ...panels[i] }; used.add(i); // Look for adjacent panels for (let j = i + 1; j < panels.length; j++) { if (used.has(j)) continue; if (isAdjacent(current, panels[j])) { current = mergePanels(current, panels[j]); used.add(j); } } merged.push(current); } return merged; } function extractCell( imageData: ImageData, gridX: number, gridY: number, cellWidth: number, cellHeight: number, ): ImageData { const startX = Math.floor(gridX * cellWidth); const startY = Math.floor(gridY * cellHeight); const width = Math.floor(cellWidth); const height = Math.floor(cellHeight); const cellData = new Uint8ClampedArray(width * height * 4); // Copy pixels for the cell region for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { const srcIdx = ((startY + y) * imageData.width + (startX + x)) * 4; const destIdx = (y * width + x) * 4; cellData[destIdx] = imageData.data[srcIdx]; cellData[destIdx + 1] = imageData.data[srcIdx + 1]; cellData[destIdx + 2] = imageData.data[srcIdx + 2]; cellData[destIdx + 3] = imageData.data[srcIdx + 3]; } } return new ImageData(cellData, width, height); } function isAdjacent(p1: Panel, p2: Panel): boolean { const tolerance = 5; // 5% tolerance for alignment // Check horizontal adjacency if ( Math.abs(p1.y - p2.y) < tolerance && Math.abs(p1.height - p2.height) < tolerance ) { return ( Math.abs(p1.x + p1.width - p2.x) < tolerance || Math.abs(p2.x + p2.width - p1.x) < tolerance ); } // Check vertical adjacency if ( Math.abs(p1.x - p2.x) < tolerance && Math.abs(p1.width - p2.width) < tolerance ) { return ( Math.abs(p1.y + p1.height - p2.y) < tolerance || Math.abs(p2.y + p2.height - p1.y) < tolerance ); } return false; } function mergePanels(p1: Panel, p2: Panel): Panel { const minX = Math.min(p1.x, p2.x); const minY = Math.min(p1.y, p2.y); const maxX = Math.max(p1.x + p1.width, p2.x + p2.width); const maxY = Math.max(p1.y + p1.height, p2.y + p2.height); return { id: p1.id, x: minX, y: minY, width: maxX - minX, height: maxY - minY, reading_order: Math.min(p1.reading_order, p2.reading_order), }; } // ADD THIS EXPORT AT THE END OF THE FILE export { detectPanelsGrid, isEmpty, mergeAdjacentPanels, extractCell, isAdjacent, mergePanels, };