using Common; using ImageMagick; using Microsoft.Win32; using Ocr.Gui.Model; using Ocr.Tesseract; using Ocr.Tesseract.Configuration; using Ocr.Tesseract.Models; using Ocr.Tesseract.Screenshots; using Ocr.Tesseract.Screenshots.Configuration; using Ocr.Tesseract.Screenshots.Threshold; using Process.Abstract.Configuration; using Process.Interface; using Serilog; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using Ocr.Tesseract.Extensions; namespace Ocr.Gui.Views; internal class ImageViewModel : INotifyPropertyChanged { /// /// Tesseract engine configuration /// public static readonly ITesseractConfiguration TesseractConfig = new TesseractScreenshotConfiguration { DataPath = "tessdata", Languages = new[] { "eng", "deu" } }; public ScreenshotProcessorConfiguration ProcessorConfig { get; } = new(); /// /// expression for extracting whole words from scan results /// public static readonly Regex WordRegex = new( @"[\w'\-]{2,}", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase ); public ImageViewModel() { ProcessorConfig.PropertyChanged += (sender, args) => Task.Run(UpdateImage); OpenFileCommand = new Command(OpenFile); SaveEditedImageCommand = new Command(SaveEditedImage); } public ImageViewModel(MagickImage image) : this() { Image = image; } private IProcessorChain MakeProcessor() { var threshold = new ThresholdAdaptiveProcessor( ProcessorConfig.ThresholdWidth, ProcessorConfig.ThresholdHeight ); var chainConfig = new ProcessorChainConfiguration() .Use(new CloneImageProcessor()); if (ProcessorConfig.EnableResizing) { chainConfig .Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh)) .Use(new ProcessingEvent(OnProcessing)); } chainConfig = chainConfig .Use(new NormalizeProcessor()) .Use(new ProcessingEvent(OnProcessing)); if (ProcessorConfig.EnableThresholding) { chainConfig = chainConfig .Use(threshold) .Use(new ProcessingEvent(OnProcessing)); } var preprocessing = chainConfig .Use(new AddBorderProcessor(ProcessorConfig.Border)) .Use(new BinarizeProcessor()) .Use(new ProcessingEvent(OnProcessing)) .Complete(new NegateCloneProcessor()); var postprocessing = new ProcessorChainConfiguration() .Use(new ConfidenceFilter(50)) .Use(new ToLowerProcessor()) .Use(new DuplicateFilter()) .Complete(new RegexFilter(WordRegex)); var scan = new TesseractProcessor(TesseractConfig); return new ProcessorChainConfiguration() .Use(preprocessing) .Use(scan) .Use(new ProcessingEvent(OnProcessed)) .Complete(postprocessing); } #region Overrides of Scanner /// protected void OnProcessing(IProcessor sender, ICollection inputs) { Application.Current.Dispatcher.Invoke(() => { foreach (var image in inputs) { Edited.Add(image.CloneImage()); } }); } /// protected void OnProcessed(IProcessor sender, ICollection inputs) { var wordStr = string.Join("\", \"", inputs); ScannedText = $"{inputs.Count} words:{Environment.NewLine}[ \"{wordStr}\" ]"; } /// public void Clear() { Application.Current.Dispatcher.Invoke(() => { ScannedText = string.Empty; Words.Clear(); Edited.Clear(); } ); } #endregion #region File Handling private void OpenFile() { var dialog = new OpenFileDialog() { InitialDirectory = Directory.GetCurrentDirectory() }; if (dialog.ShowDialog() == true) { Image = new MagickImage(dialog.FileName); UpdateImage(); } } private void SaveEditedImage() { var basePath = AppDomain.CurrentDomain.BaseDirectory; for (var i = 0; i < Edited.Count; i++) { Edited[i].Write(Path.Combine(basePath, $"edited_{i}.png")); } Log.Information($"Saved image to '{basePath}'"); System.Diagnostics.Process.Start( "explorer.exe", Path.GetDirectoryName(basePath) ?? string.Empty ); } #endregion #region Updating data private void UpdateImage() { Task.Run(() => { IsIdle = false; var scanner = new ScreenshotScanner(MakeProcessor()); Clear(); if (Image != null) { scanner.Process(new[] { Image }); } Application.Current.Dispatcher.Invoke(() => { var confidence = 0f; foreach (var word in scanner.Lookup.Keys) { confidence += word.Confidence; Words.Add(word); } Confidence = confidence / scanner.Lookup.Keys.Count; } ); IsIdle = true; }); } #endregion #region Properties private float _confidence; private MagickImage? _image; private bool _isIdle; private string _scannedText = string.Empty; public string ScannedText { get => _scannedText; set { if (value == _scannedText) { return; } _scannedText = value; OnPropertyChanged(); } } public bool IsIdle { get => _isIdle; set { if (value == _isIdle) { return; } _isIdle = value; OnPropertyChanged(); } } public float Confidence { get => _confidence; set { _confidence = value; OnPropertyChanged(); } } public ObservableCollection Edited { get; } = new(); public MagickImage? Image { get => _image; set { _image = value; OnPropertyChanged(); } } public ObservableCollection Words { get; } = new(); #endregion Properties #region Commands public ICommand OpenFileCommand { get; private set; } public ICommand SaveEditedImageCommand { get; private set; } #endregion Commands #region INotifyPropertyChanged public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion INotifyPropertyChanged }