Files
gridpilot.gg/packages/infrastructure/adapters/automation/ScreenRecognitionService.ts

124 lines
3.7 KiB
TypeScript

import { screen, Region } from '@nut-tree-fork/nut-js';
import type { ScreenRegion } from '../../../domain/value-objects/ScreenRegion';
import type { ScreenCaptureResult } from '../../../application/ports/IScreenAutomation';
import type { ILogger } from '../../../application/ports/ILogger';
import { NoOpLogAdapter } from '../logging/NoOpLogAdapter';
/**
* Service for capturing screen regions using nut.js.
* Provides screen capture functionality for template matching and visual automation.
*/
export class ScreenRecognitionService {
private logger: ILogger;
constructor(logger?: ILogger) {
this.logger = logger ?? new NoOpLogAdapter();
}
/**
* Capture the entire screen.
* @returns ScreenCaptureResult with image buffer data
*/
async captureFullScreen(): Promise<ScreenCaptureResult> {
const startTime = Date.now();
try {
const width = await screen.width();
const height = await screen.height();
this.logger.debug('Capturing full screen', { width, height });
const image = await screen.grab();
const data = await image.toRGB();
const durationMs = Date.now() - startTime;
this.logger.debug('Screen capture completed', { durationMs, width, height });
return {
success: true,
data: Buffer.from(data.data),
width: data.width,
height: data.height,
};
} catch (error) {
const errorMsg = `Screen capture failed: ${error}`;
this.logger.error('Screen capture failed', error instanceof Error ? error : new Error(errorMsg));
return {
success: false,
error: errorMsg,
};
}
}
/**
* Capture a specific region of the screen.
* @param region - The rectangular region to capture
* @returns ScreenCaptureResult with image buffer data
*/
async captureRegion(region: ScreenRegion): Promise<ScreenCaptureResult> {
const startTime = Date.now();
try {
this.logger.debug('Capturing screen region', { region });
const nutRegion = new Region(region.x, region.y, region.width, region.height);
const image = await screen.grabRegion(nutRegion);
const data = await image.toRGB();
const durationMs = Date.now() - startTime;
this.logger.debug('Region capture completed', { durationMs, region });
return {
success: true,
data: Buffer.from(data.data),
width: data.width,
height: data.height,
};
} catch (error) {
const errorMsg = `Region capture failed: ${error}`;
this.logger.error('Region capture failed', error instanceof Error ? error : new Error(errorMsg), { region });
return {
success: false,
error: errorMsg,
};
}
}
/**
* Get the current screen dimensions.
* @returns Object with width and height, or null on error
*/
async getScreenDimensions(): Promise<{ width: number; height: number } | null> {
try {
const width = await screen.width();
const height = await screen.height();
return { width, height };
} catch (error) {
this.logger.error('Failed to get screen dimensions', error instanceof Error ? error : new Error(String(error)));
return null;
}
}
/**
* Convert ScreenRegion to nut.js Region.
* Utility method for internal use.
*/
toNutRegion(region: ScreenRegion): Region {
return new Region(region.x, region.y, region.width, region.height);
}
/**
* Convert nut.js Region to ScreenRegion.
* Utility method for internal use.
*/
fromNutRegion(nutRegion: Region): ScreenRegion {
return {
x: nutRegion.left,
y: nutRegion.top,
width: nutRegion.width,
height: nutRegion.height,
};
}
}