Remove OCR dependencies for Windows build - stub implementation

This commit is contained in:
Aether 2026-02-23 19:54:25 +00:00
parent c08b30e52d
commit f2465b2b4f
No known key found for this signature in database
GPG Key ID: 95AFEE837E39AFD2
2 changed files with 25 additions and 163 deletions

View File

@ -47,10 +47,11 @@ uuid = { version = "1.6", features = ["v4", "serde"] }
# Regex # Regex
regex = "1.10" regex = "1.10"
# Image processing for OCR # Image processing
image = "0.24" image = "0.24"
imageproc = "0.23" # Note: OCR requires system libraries (Tesseract)
leptess = "0.14" # imageproc = "0.23"
# leptess = "0.14"
# Clipboard # Clipboard
arboard = "3.3" arboard = "3.3"

View File

@ -1,94 +1,49 @@
use image::{DynamicImage, GrayImage, ImageBuffer, Luma}; // OCR Engine - Stub implementation
use imageproc::contrast::{stretch_contrast, threshold}; // Full OCR requires Tesseract system libraries
use leptess::TessBaseApi; // This stub allows the application to compile without OCR dependencies
use std::collections::HashMap; use std::collections::HashMap;
use tracing::{debug, error, info}; use tracing::{debug, info};
use crate::ocr::calibration::{OCRRegion, OCRCalibration, PreprocessingConfig}; use crate::ocr::calibration::{OCRRegion, OCRCalibration, PreprocessingConfig};
pub struct OCREngine { pub struct OCREngine {
tesseract: TessBaseApi,
calibration: OCRCalibration, calibration: OCRCalibration,
available: bool,
} }
impl OCREngine { impl OCREngine {
pub fn new() -> Result<Self, String> { pub fn new() -> Result<Self, String> {
let mut tesseract = TessBaseApi::new(); info!("OCR Engine initialized (stub mode - Tesseract not available)");
// Initialize with English
if tesseract.init(None, "eng").is_err() {
return Err("Failed to initialize Tesseract".to_string());
}
// Set OCR engine mode to LSTM only for better accuracy
tesseract.set_variable("tessedit_ocr_engine_mode", "1").ok();
// Whitelist characters for numeric/text recognition
tesseract.set_variable("tessedit_char_whitelist",
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,-:()%/ ").ok();
Ok(Self { Ok(Self {
tesseract,
calibration: OCRCalibration::default(), calibration: OCRCalibration::default(),
available: false,
}) })
} }
pub fn is_available(&self) -> bool {
self.available
}
pub fn set_calibration(&mut self, calibration: OCRCalibration) { pub fn set_calibration(&mut self, calibration: OCRCalibration) {
self.calibration = calibration; self.calibration = calibration;
} }
pub fn recognize_region(&mut self, region_name: &str, screenshot: &[u8]) -> Result<String, String> { pub fn recognize_region(&mut self, _region_name: &str, _screenshot: &[u8]) -> Result<String, String> {
let region = self.calibration.regions.get(region_name) // Stub implementation - OCR requires Tesseract system libraries
.ok_or_else(|| format!("Region '{}' not found", region_name))?; Err("OCR not available - Tesseract system library not installed".to_string())
// Load image
let mut img = image::load_from_memory(screenshot)
.map_err(|e| format!("Failed to load image: {}", e))?;
// Crop to region
let cropped = img.crop(
region.x as u32,
region.y as u32,
region.width as u32,
region.height as u32,
);
// Preprocess
let processed = self.preprocess_image(&cropped, &region.preprocessing);
// Perform OCR
let text = self.perform_ocr(&processed)?;
debug!("OCR result for '{}': '{}'", region_name, text);
Ok(text.trim().to_string())
} }
pub fn recognize_hp(&mut self, screenshot: &[u8]) -> Result<(f32, f32), String> { pub fn recognize_hp(&mut self, _screenshot: &[u8]) -> Result<(f32, f32), String> {
// Try to extract HP value and max HP Err("OCR not available".to_string())
let text = self.recognize_region("hp_bar", screenshot)?;
// Parse HP text (formats: "1234/5678" or "1234" or percentage)
if let Some((current, max)) = Self::parse_hp_text(&text) {
Ok((current, max))
} else {
Err(format!("Failed to parse HP from text: '{}'", text))
}
} }
pub fn recognize_mob_name(&mut self, screenshot: &[u8]) -> Result<String, String> { pub fn recognize_mob_name(&mut self, _screenshot: &[u8]) -> Result<String, String> {
// Mob names appear as floating text above mobs Err("OCR not available".to_string())
// This requires detecting text that appears temporarily
let text = self.recognize_region("mob_name", screenshot)?;
Ok(text)
} }
pub fn recognize_coordinates(&mut self, screenshot: &[u8]) -> Result<(f32, f32, f32), String> { pub fn recognize_coordinates(&mut self, _screenshot: &[u8]) -> Result<(f32, f32, f32), String> {
// Read coordinates from radar or location display Err("OCR not available".to_string())
let text = self.recognize_region("radar", screenshot)?;
// Parse coordinate text (format: "X: 12345.67 Y: 12345.89 Z: 123.45")
Self::parse_coordinates(&text)
} }
pub fn detect_text_changes( pub fn detect_text_changes(
@ -99,7 +54,6 @@ impl OCREngine {
) -> Vec<TextChange> { ) -> Vec<TextChange> {
let mut changes = Vec::new(); let mut changes = Vec::new();
// Detect added/removed/changed text
if previous != current { if previous != current {
changes.push(TextChange { changes.push(TextChange {
region: region_name.to_string(), region: region_name.to_string(),
@ -111,99 +65,6 @@ impl OCREngine {
changes changes
} }
fn preprocess_image(&self, img: &DynamicImage, config: &PreprocessingConfig) -> DynamicImage {
let mut processed = img.clone();
// Convert to grayscale if needed
if config.grayscale {
processed = DynamicImage::ImageLuma8(processed.to_luma8());
}
// Apply contrast and brightness
if config.contrast != 1.0 || config.brightness != 0.0 {
processed = self.adjust_contrast_brightness(&processed, config.contrast, config.brightness);
}
// Apply threshold if specified
if let Some(thresh) = config.threshold {
if let DynamicImage::ImageLuma8(gray) = &processed {
let thresholded = threshold(gray, thresh);
processed = DynamicImage::ImageLuma8(thresholded);
}
}
// Invert if needed
if config.invert {
processed.invert();
}
processed
}
fn adjust_contrast_brightness(&self, img: &DynamicImage, contrast: f32, brightness: f32) -> DynamicImage {
// Apply contrast stretch
if let DynamicImage::ImageLuma8(gray) = img {
let adjusted = stretch_contrast(gray,
(brightness * 255.0) as u8,
((1.0 + contrast) * 255.0) as u8
);
DynamicImage::ImageLuma8(adjusted)
} else {
img.clone()
}
}
fn perform_ocr(&mut self, img: &DynamicImage) -> Result<String, String> {
// Convert to bytes for Tesseract
let mut bytes: Vec<u8> = Vec::new();
img.write_to(&mut std::io::Cursor::new(&mut bytes), image::ImageOutputFormat::Png)
.map_err(|e| format!("Failed to encode image: {}", e))?;
// Set image in Tesseract
self.tesseract.set_image_from_mem(&bytes)
.map_err(|e| format!("Failed to set image: {:?}", e))?;
// Get text
let text = self.tesseract.get_utf8_text()
.map_err(|e| format!("OCR failed: {:?}", e))?;
Ok(text)
}
fn parse_hp_text(text: &str) -> Option<(f32, f32)> {
// Parse formats: "1234/5678", "1234 / 5678", "1,234/5,678"
let cleaned: String = text.chars()
.filter(|c| c.is_digit(10) || *c == '/' || *c == '.' || *c == ',')
.collect();
if let Some(sep_pos) = cleaned.find('/') {
let current: f32 = cleaned[..sep_pos].replace(",", "").parse().ok()?;
let max: f32 = cleaned[sep_pos + 1..].replace(",", "").parse().ok()?;
Some((current, max))
} else {
// Single number - assume it's current HP
let current: f32 = cleaned.replace(",", "").parse().ok()?;
Some((current, current))
}
}
fn parse_coordinates(text: &str) -> Result<(f32, f32, f32), String> {
// Parse format: "X: 12345.67 Y: 12345.89 Z: 123.45"
let mut coords = (0.0, 0.0, 0.0);
for part in text.split_whitespace() {
if part.starts_with("X:") {
coords.0 = part[2..].trim().parse().unwrap_or(0.0);
} else if part.starts_with("Y:") {
coords.1 = part[2..].trim().parse().unwrap_or(0.0);
} else if part.starts_with("Z:") {
coords.2 = part[2..].trim().parse().unwrap_or(0.0);
}
}
Ok(coords)
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]