import { AardPerformanceData } from './AardTimers'; export class AardDebugUi { aard: any; uiAnchorElement: HTMLDivElement; pauseOnArCheck: boolean = false; uiVisibility: any = {}; constructor(aard: any) { this.aard = aard; this.uiVisibility = { detectionDetails: aard.settings.active.ui.dev.aardDebugOverlay.showDetectionDetails }; (window as any).ultrawidify_uw_aard_debug_tools = { enableStopOnChange: () => this.changePauseOnCheck(true), disableStopOnChange: () => this.changePauseOnCheck(false), resumeVideo: () => this.resumeVideo(), step: () => this.aard.step() } } initContainer() { const div = document.createElement('div'); div.id = 'uw-aard-debug-ui-container'; div.innerHTML = `

Aard debug overlay

AARD IN
Black level sample
Guard line (middle/corner) OK
Guard line (middle/corner) violation
Image line — image, no image
Edge scan — probe, hit
Slope test — ok, fail
Debug results:

            
AARD RESULT
`; document.body.appendChild(div); this.uiAnchorElement = div; document.getElementById('uw-aard-debug-ui_enable-stop-on-change').onclick = () => this.changePauseOnCheck(true); document.getElementById('uw-aard-debug-ui_disable-stop-on-change').onclick = () => this.changePauseOnCheck(false); document.getElementById('uw-aard-debug-ui_resume-video').onclick = () => this.resumeVideo(); document.getElementById('uw-aard-debug-ui_enable-step').onclick = () => this.aard.step(); document.getElementById('uw-aard-debug-ui_enable-step-nocache').onclick = () => this.aard.step({noCache: true}); document.getElementById('uw-aard-debug-ui_close-overlay').onclick = () => (this.aard as any).hideDebugCanvas(); document.getElementById('uw-aard-debug_show-detection-details').onclick = () => {this.uiVisibility.detectionDetails = true; this.setOverlayVisibility();}; document.getElementById('uw-aard-debug_hide-detection-details').onclick = () => {this.uiVisibility.detectionDetails = false; this.setOverlayVisibility();}; this.setOverlayVisibility(); } changePauseOnCheck(pauseOnChange: boolean) { this.pauseOnArCheck = pauseOnChange; document.getElementById("uw-aard-debug-ui_enable-stop-on-change").style.display = pauseOnChange ? "none" : ""; document.getElementById("uw-aard-debug-ui_disable-stop-on-change").style.display = pauseOnChange ? "" : "none"; } destroyContainer() { this.uiAnchorElement.remove(); } attachCanvases(sample: HTMLCanvasElement, debug: HTMLCanvasElement) { const sampleCanvasParent = document.getElementById('uw-aard-debug_aard-sample-canvas'); sampleCanvasParent.appendChild(sample); const debugCanvasParent = document.getElementById('uw-aard-debug_aard-output'); debugCanvasParent.appendChild(debug); } resumeVideo() { (this.aard as any).video.play(); this.aard.start(); } private updatePerformanceResults() { const previewDiv = document.getElementById('uw-aard-debug_performance-mini'); const popupDiv = document.getElementById('uw-aard-debug_performance-popup'); const previewContent = `
Current:
${this.generateMiniGraphBar(this.aard.timer.current)}
Average:
${this.generateMiniGraphBar(this.aard.timer.average)}
Last chg.:
${this.generateMiniGraphBar(this.aard.timer.lastChange)}
`; const popupContent = `

Detailed performance analysis:

${this.generateRawTimes(this.aard.timer.average)}
Stage times (not cumulative):
Current:
${this.generateMiniGraphBar(this.aard.timer.current, true)}
${this.generateRawTimes(this.aard.timer.average)}
Stage times (not cumulative):
Average:
${this.generateMiniGraphBar(this.aard.timer.average, true)}
${this.generateRawTimes(this.aard.timer.average)}
Last change:
${this.generateMiniGraphBar(this.aard.timer.lastChange, true)}
` previewDiv.innerHTML = previewContent; popupDiv.innerHTML = popupContent; } private getBarLabel(width: number, leftOffset: number, topOffset: number, label: string, detailed: boolean, extraStyles?: string) { if (!detailed) { return ''; } let offsets: string; let text: string = ''; if (leftOffset + width < 80) { // at the end of the bar offsets = `left: ${leftOffset + width}%;`; } else { if (width < 15 && leftOffset < 100) { // before the bar offsets = `right: ${100 - leftOffset}%;`; text = 'color: #fff;'; } else { // inside the bar, aligned to right offsets = `right: ${Math.max(100 - (leftOffset + width), 0)}%;` } } return `
${label}
`; } private generateRawTimes(timer) { return ` Raw times (cumulative; -1 = test was skipped):
draw ${timer.draw.toFixed(2)}ms get data ${timer.getImage.toFixed(2)}ms black lv. ${timer.fastBlackLevel.toFixed(2)}ms guard/image ${timer.guardLine.toFixed(3)}ms edge ${timer.edgeScan.toFixed(2)}ms gradient ${timer.gradient.toFixed(3)}ms post ${timer.scanResults.toFixed(2)}ms subtitle ${timer.subtitleScan.toFixed(2)}ms `; } private generateMiniGraphBar(perf: AardPerformanceData, detailed: boolean = false) { if (!perf) { return ` n/a `; } let total = 0; const draw = Math.max(perf.draw, 0); total += draw; const getImageStart = draw; const getImage = Math.max(perf.getImage - total, 0); total += getImage; const fastBlackLevelStart = getImageStart + getImage; const fastBlackLevel = Math.max(perf.fastBlackLevel - total, 0); total += fastBlackLevel; const guardLineStart = fastBlackLevelStart + fastBlackLevel; const guardLine = Math.max(perf.guardLine - total, 0); total += guardLine; const edgeScanStart = guardLineStart + guardLine; const edgeScan = Math.max(perf.edgeScan - total, 0); total += edgeScan; const gradientStart = edgeScanStart + edgeScan; const gradient = Math.max(perf.gradient - total, 0); total += gradient; const scanResultsStart = gradientStart + gradient; const scanResults = Math.max(perf.scanResults - total, 0); total += scanResults; const subtitleScanStart = scanResultsStart + scanResults; const subtitleScan = Math.max(perf.scanResults - total, 0); total += subtitleScan; return `
${detailed ? '' : `${total.toFixed()} ms`}
${detailed ? `
` : ''}
${this.getBarLabel(draw, 0, 2, `draw: ${draw.toFixed(2)} ms`, detailed)}
${this.getBarLabel(getImage, getImageStart, 14, `get data: ${getImage.toFixed(2)} ms`, detailed)}
${this.getBarLabel(fastBlackLevel, fastBlackLevelStart, 26, `black level: ${fastBlackLevel.toFixed(2)} ms`, detailed)};
${this.getBarLabel(guardLine, guardLineStart, 38, `guard/image line: ${guardLine.toFixed(2)} ms`, detailed)}
${this.getBarLabel(edgeScan, edgeScanStart, 50, `edge scan (/w validation): ${edgeScan.toFixed(2)} ms`, detailed)}
${this.getBarLabel(gradient, gradientStart, 62, `gradient: ${gradient.toFixed(2)} ms`, detailed)}
${this.getBarLabel(scanResults, scanResultsStart, 74, `scan results processing: ${scanResults.toFixed(2)} ms`, detailed)} ${this.getBarLabel(0, scanResults + scanResultsStart, 88, `total: ${total.toFixed(2)} ms`, detailed, 'color: #fff;')}
${this.getBarLabel(scanResults, scanResultsStart, 74, `scan results processing: ${subtitleScan.toFixed(2)} ms`, detailed)} ${this.getBarLabel(0, scanResults + scanResultsStart, 88, `total: ${total.toFixed(2)} ms`, detailed, 'color: #fff;')}
60fps
30fps
`; } _lastAr: undefined; updateTestResults(testResults) { this.updatePerformanceResults(); if (testResults.aspectRatioUpdated && this.pauseOnArCheck) { (this.aard as any).video.pause(); this.aard.stop(); } const resultsDiv = document.getElementById('uw-aard-results'); const ar = testResults.activeAspectRatio.toFixed(3); let out = ` LAST STAGE: ${testResults.lastStage} | black level: ${testResults.blackLevel}, threshold: ${testResults.blackThreshold} -- ASPECT RATIO Active: ${ar}, changed since last check? ${testResults.aspectRatioUpdated} letterbox width: ${testResults.letterboxWidth} offset ${testResults.letterboxOffset}
(last: ${this._lastAr}) image in black level probe (aka "not letterbox"): ${testResults.notLetterbox} `; this._lastAr = ar; if (testResults.notLetterbox) { resultsDiv.innerHTML = out; return; } out = `${out} -- UNCERTAIN FLAGS AR: ${testResults.aspectRatioUncertain} (reason: ${testResults.aspectRatioUncertainReason ?? 'n/a'}); top row: ${testResults.topRowUncertain}; bottom row: ${testResults.bottomRowUncertain}${ testResults.aspectRatioInvalid ? `\nINVALID_AR (reason: ${testResults.aspectRatioInvalidReason ?? 'n/a'})` : ''} -- GUARD & IMAGE LINE bottom guard: ${testResults.guardLine.bottom} image: ${testResults.guardLine.invalidated ? 'n/a' : testResults.imageLine.bottom} top guard: ${testResults.guardLine.top} image: ${testResults.guardLine.invalidated ? 'n/a' : testResults.imageLine.top} guard line ${testResults.guardLine.invalidated ? 'INVALIDATED' : 'valid'} image line ${testResults.guardLine.invalidated ? '' : testResults.imageLine.invalidated ? 'INVALIDATED' : 'valid'} corner invalidations (invalid pixels -> verdict) LEFT CENTER RIGHT bottom: ${testResults.guardLine.cornerPixelsViolated[0]} → ${testResults.guardLine.cornerViolated[0] ? '❌' : '◽'} ${testResults.guardLine.cornerPixelsViolated[1]} → ${testResults.guardLine.cornerViolated[1] ? '❌' : '◽'} top: ${testResults.guardLine.cornerPixelsViolated[2]} → ${testResults.guardLine.cornerViolated[2] ? '❌' : '◽'} ${testResults.guardLine.cornerPixelsViolated[3]} → ${testResults.guardLine.cornerViolated[3] ? '❌' : '◽'} -- AR SCAN ${testResults.lastStage < 1 ? ` DID NOT RUN THIS FRAME` : ` LEFT CENTER RIGHT CANDIDATE BOTTOM distance: ${testResults.aspectRatioCheck.bottomRows[0]} ${testResults.aspectRatioCheck.bottomRows[1]} ${testResults.aspectRatioCheck.bottomRows[2]} ${testResults.aspectRatioCheck.bottomCandidate} quality: ${testResults.aspectRatioCheck.bottomQuality[0]} ${testResults.aspectRatioCheck.bottomQuality[1]} ${testResults.aspectRatioCheck.bottomQuality[2]} ${testResults.aspectRatioCheck.bottomCandidateQuality} TOP distance: ${testResults.aspectRatioCheck.topRows[0]} ${testResults.aspectRatioCheck.topRows[1]} ${testResults.aspectRatioCheck.topRows[2]} ${testResults.aspectRatioCheck.topCandidate} quality: ${testResults.aspectRatioCheck.topQuality[0]} ${testResults.aspectRatioCheck.topQuality[1]} ${testResults.aspectRatioCheck.topQuality[2]} ${testResults.aspectRatioCheck.topCandidateQuality} Diff matrix: R-L C-R C-L bottom: ${testResults.aspectRatioCheck.bottomRowsDifferenceMatrix[0]} ${testResults.aspectRatioCheck.bottomRowsDifferenceMatrix[1]} ${testResults.aspectRatioCheck.bottomRowsDifferenceMatrix[2]} top: ${testResults.aspectRatioCheck.topRowsDifferenceMatrix[0]} ${testResults.aspectRatioCheck.topRowsDifferenceMatrix[1]} ${testResults.aspectRatioCheck.topRowsDifferenceMatrix[2]} `} `; resultsDiv.innerHTML = out; } private setOverlayVisibility() { document.getElementById('uw-aard-debug-ui_body').style.display = this.uiVisibility.detectionDetails ? 'flex' : 'none'; document.getElementById('uw-aard-debug_hide-detection-details').style.display = this.uiVisibility.detectionDetails ? '' : 'none'; document.getElementById('uw-aard-debug_show-detection-details').style.display = this.uiVisibility.detectionDetails ? 'none' : ''; } }