Слежение за участком экрана на C#


На днях написал небольшую программу на заказ. Суть приложения заключалась в слежении за определённым участком экрана. Необходимо было следить за выбранным участком, раз в секунду проверяя его изменения. Что-то типа бота, но гораздо проще. Программа должна была реагировать в на любые изменения на экране, то есть если мы перейдём со страницы сайта, откроем другую вкладку, то сразу же выскакивает окно и звуковое оповещение. Программа получилась простая, но с парой интересных моментов, о которых я и расскажу ниже.



Сначала алгоритм решения задачи – создания программы. Он прост:

  1. Выделяем кусок экрана и сохраняем его в битмап
  2. Запускаем таймер и сравниваем его раз в секунду с такими же кусками
  3. Если получилось расхождение – звук на полную громкость, окно на передний план
Выглядит просто, не правда ли? На самом деле все просто и есть.

Первый момент – как выделить кусок экрана? Первое, что приходит в голову – это рисовать курсором рамку. Вообще, да, это можно сделать. Но потребуется высчитывать координаты курсора, рисовать линии на рабочем столе (или стороннем приложении) – это все реализуемо, но, честно говоря, довольно муторно и нестабильно. Благо заказчик попался нетребовательный, интерфейс я сам рисовал, так что упростил себе задачу.

Я просто сделал прозрачное окно (форму) и закинул на него picturebox (прозрачный, с рамкой) и кнопку (не прозрачную):

   private void Form1_Load(object sender, EventArgs e)
        {
            this.AllowTransparency = true;
            this.BackColor = Color.AliceBlue; 
            this.TransparencyKey = this.BackColor;
        }
Высчитать координаты и размеры внутри picturebox также несложно:

	Point location = pictureBox1.PointToScreen(Point.Empty);
                x = location.X+5;
                y = location.Y+5;
                w = pictureBox1.Width - 5;
                h = pictureBox1.Height - 5;
 player.SoundLocation = "Sound_15750.wav";
 
Получаем скриншот данного куска экрана:


bmp1 = new Bitmap(w, h); using (Graphics g = Graphics.FromImage(bmp1)) { g.CopyFromScreen(x, y, 0, 0, new Size(w, h)); } bmp1.Save("scrstart.png", System.Drawing.Imaging.ImageFormat.Png);
Дальше надо запустить таймер (не забыв кинуть на форму данный элемент) и свернуть окно на панель задач:

InitializeTimer(); this.WindowState = FormWindowState.Minimized; А вот что делает таймер у нас? Он раз в секунду берет этот же кусок экрана и сравнивает его битмап с первым изображением:

     private void Timer1_Tick(object Sender, EventArgs e)
        {
            bmp1 = new Bitmap(Image.FromFile("scrstart.png"));
            Bitmap bmp2 = new Bitmap(w, h);
            using (Graphics g = Graphics.FromImage(bmp2))
            {
                g.CopyFromScreen(x, y, 0, 0, new Size(w, h));
            }
            pt = GetMismatchPoint(bmp1, bmp2);
            if (pt != null) {
                timer1.Enabled = false;
                player.Play();
                this.WindowState = FormWindowState.Normal;
                this.TopMost = true;
            }           
        }
И если оно отличается, то останавливает таймер, запускает музыку, а окно выскаивает на передний план. Тут интересный момент: сравнивать попиксельно нерационально и ресурсоемко. Поэтому возьмем готовый класс оболочку ImageWrapper и используем вот такой метод:

	Point? GetMismatchPoint(Bitmap bmp1, Bitmap bmp2)
        {
            using (var wr1 = new ImageWrapper(bmp1))
            using (var wr2 = new ImageWrapper(bmp2))
                foreach (var p in wr1)
                    if (wr1[p] != wr2[p])
                        return p;

            return null;
        }
Работает очень шустро и не напрягает процессор: загрузка процессора 0 процентов, а паямти - 7 МБ. Не так уж и много, не правда ли? Еще один момент. Заказчик попросил, чтобы программа воспроизводила звук на полную громкость. Я не придумал ничего лучшего, как просто воспользоваться аппаратными методами:

    private const int APPCOMMAND_VOLUME_MUTE = 0x80000;
        private const int APPCOMMAND_VOLUME_UP = 0xA0000;
        private const int APPCOMMAND_VOLUME_DOWN = 0x90000;
        private const int WM_APPCOMMAND = 0x319;

        [DllImport("user32.dll")]
        public static extern IntPtr SendMessageW(IntPtr hWnd, int Msg,
            IntPtr wParam, IntPtr lParam);
        private void VolUp()
        {
            for (int i=0; i<50; i++) {
                SendMessageW(this.Handle, WM_APPCOMMAND, this.Handle,
                    (IntPtr)APPCOMMAND_VOLUME_UP);
            }
        }
Работает? Ура, все работает. Задаем окном некий участок экрана и заставляем програму следить за ним. Отправляем заказчику приложение и ... и не работает. Оказывается, на компьютере заказчика программа сразу же выдавала алерт - как будто что-то на экране изменилось. Но не изменялось ничего. В чем может быть проблема?

На первый взгляд ничего не должно глючить - просто нечему. Никакой привязки к железу, системе или разрешению экрана нет. Первая мысль была такой: возможно, что программа не успевает прочитать и занести в битмап первое изображение и соответственно, сравнивает разные вещи. Однако, сделал задержку таймера до 10 секунд - когда точно уже все должно успеть обработаться даже на самых слабых машинах - результат был одинаковый. То есть не работает так, как надо.

Продолжаем копать дальше. Переносим программу на другой ноутбук – чтобы понять, почему все происходит – там тоже не работает! Срабатывание оповещения в программе происходит из-за того, что картинки кажутся её разными. Давайте попробуем сравнить их, то есть заставим сохранить и второе изображение на диск. Реализуем и…видим кое-что интересное:



Слева – первоначальный кусок экрана, справа – полученный через одну секунду. Как видим, тут есть тень от picturebox – вот поэтому и приложение не признает изображения одинаковыми. Как решить проблему? Все просто: убираем по 10 пикселей справа и снизу – чтобы не захватывать тень при скриншоте. И ура – все заработало!

Итак, если вам требуется написать программу на заказ для личного пользования, создать простого бота или что-то еще, то вы всегда можете написать мне. Мои расценки невысоки.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегизаметки, си шарп, личное, услуги, программы




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.




Взаимодействие и синхронизация потоков
JavaScript фреймворки
AI Factory's Chess, уровень 11, 23 мая 2016 - С. Визгорев