Как сделать paint на c
Перед тем, как начать заниматься рисованием линий и фигур, отрисовкой текста или отображением изображений и управления ими с помощью GDI+, необходимо создать объект Graphics. Объект Graphics представляет собой поверхность рисования GDI+ и используется для создания графических изображений.
Существует два этапа работы с графикой:
Использование объекта Graphics для рисования линий и фигур, отрисовки текста или отображения изображений и управления ими.
Создание графического объекта
Графический объект можно создать несколькими способами.
Как создать графический объект
Получите ссылку на графический объект как часть PaintEventArgs события формы или элемента управления Paint. Обычно ссылка на графический объект таким образом берется при создании кода рисования для элемента управления. Аналогичным образом можно также получить графический объект в качестве свойства PrintPageEventArgs при обработке события PrintPage для объекта PrintDocument.
Вызовите метод элемента управления или формы CreateGraphics, чтобы получить ссылку на объект Graphics, представляющий собой область рисования этого элемента управления или формы. Используйте этот метод, если необходимо нарисовать на форме или элементе управления, которые уже существуют.
Создайте объект Graphics из любого объекта, наследуемого от Image. Этот вариант подходит если вы хотите изменить уже существующее изображение.
Необходимые действия будут более подробно описаны в следующих разделах.
PaintEventArgs в обработчике событий Paint
При программировании PaintEventHandler для элементов управления или PrintPage для объекта PrintDocument, графический объект предоставляется в качестве одного из свойств PaintEventArgs или PrintPageEventArgs.
Получение ссылки на графический объект из PaintEventArgs в событии Paint
Назначьте переменную для ссылки на объект Graphics, переданный как часть PaintEventArgs.
Введите код, чтобы нарисовать форму или элемент управления.
В следующем примере показано, как ссылаться на объект Graphics из PaintEventArgs в событии Paint :
Метод CreateGraphics
Также можно воспользоваться методом элемента управления или формы CreateGraphics, чтобы получить ссылку на объект Graphics, представляющий собой область рисования этого элемента управления или формы.
Создание графического объекта с помощью метода CreateGraphics
Вызовите метод CreateGraphics для формы или элемента управления, на которых требуется отрисовка графики.
Создание из объекта изображения
Кроме того, можно создать графический объект из любого объекта, производного от класса Image.
Как создать графический объект на основе изображения
Вызовите метод Graphics.FromImage, указав имя переменной изображения, из которой требуется создать объект Graphics.
В приведенном ниже примере показано использование объекта Bitmap:
Объекты Graphics можно создавать только из неиндексированных файлов формата .bmp, например 16-разрядных, 24-разрядных и 32-разрядных. Каждый пиксель неиндексированных файлов .bmp содержит цвет. В индексированных .bmp файлах же каждый пиксель содержит индекс таблицы цветов.
Рисование и обработка фигур и изображений
Объект Graphics после создания может использоваться для рисования линий и фигур, отрисовки текста или отображения изображений и управления ими. Вот основные объекты, которые используются с объектом Graphics:
Класс Pen — используется для рисования линий, структурирования фигур или отрисовки других геометрических представлений.
Класс Brush — используется для заполнения областей графики, в частности для заполненных фигур, изображений или текста.
Класс Font — предоставляет собой описание фигур, используемых при отрисовке текста.
Структура Color — представляет различные отображаемые цвета.
Использование созданного графического объекта
Обратитесь к соответствующему объекту, указанному выше, чтобы нарисовать необходимые объекты.
Before you can draw lines and shapes, render text, or display and manipulate images with GDI+, you need to create a Graphics object. The Graphics object represents a GDI+ drawing surface, and is the object that is used to create graphical images.
There are two steps in working with graphics:
Creating a Graphics object.
Using the Graphics object to draw lines and shapes, render text, or display and manipulate images.
Creating a Graphics Object
A graphics object can be created in a variety of ways.
To create a graphics object
Receive a reference to a graphics object as part of the PaintEventArgs in the Paint event of a form or control. This is usually how you obtain a reference to a graphics object when creating painting code for a control. Similarly, you can also obtain a graphics object as a property of the PrintPageEventArgs when handling the PrintPage event for a PrintDocument.
Call the CreateGraphics method of a control or form to obtain a reference to a Graphics object that represents the drawing surface of that control or form. Use this method if you want to draw on a form or control that already exists.
Create a Graphics object from any object that inherits from Image. This approach is useful when you want to alter an already existing image.
The following sections give details about each of these processes.
PaintEventArgs in the Paint Event Handler
When programming the PaintEventHandler for controls or the PrintPage for a PrintDocument, a graphics object is provided as one of the properties of PaintEventArgs or PrintPageEventArgs.
To obtain a reference to a Graphics object from the PaintEventArgs in the Paint event
Declare the Graphics object.
Assign the variable to refer to the Graphics object passed as part of the PaintEventArgs.
Insert code to paint the form or control.
The following example shows how to reference a Graphics object from the PaintEventArgs in the Paint event:
CreateGraphics Method
You can also use the CreateGraphics method of a control or form to obtain a reference to a Graphics object that represents the drawing surface of that control or form.
To create a Graphics object with the CreateGraphics method
Call the CreateGraphics method of the form or control upon which you want to render graphics.
Create from an Image Object
Additionally, you can create a graphics object from any object that derives from the Image class.
To create a Graphics object from an Image
Call the Graphics.FromImage method, supplying the name of the Image variable from which you want to create a Graphics object.
The following example shows how to use a Bitmap object:
You can only create Graphics objects from nonindexed .bmp files, such as 16-bit, 24-bit, and 32-bit .bmp files. Each pixel of nonindexed .bmp files holds a color, in contrast to pixels of indexed .bmp files, which hold an index to a color table.
Drawing and Manipulating Shapes and Images
The Pen class—Used for drawing lines, outlining shapes, or rendering other geometric representations.
The Brush class—Used for filling areas of graphics, such as filled shapes, images, or text.
The Font class—Provides a description of what shapes to use when rendering text.
Пользовательское рисование элементов управления — одна из многих сложных задач, которые можно с легкостью выполнить в Windows Forms. При разработке пользовательского элемента управления доступно множество параметров, которые управляют графическим отображением элемента управления. Если вы создаете настраиваемый элемент управления, то есть элемент управления, который наследуется от Control, необходимо предоставить код для преобразования его графического представления для просмотра.
При создании составного элемента управления, который является элементом управления, наследуемым от UserControl или одного из существующих элементов управления Windows Forms, можно переопределить стандартное графическое представление и предоставить собственный графический код.
Если вы хотите предоставить настраиваемый рендеринг для существующего элемента управления без создания нового элемента управления, набор параметров становится более ограниченным. Однако по-прежнему существует широкий набор возможностей графического представления ваших элементов управления и приложений.
При отрисовке элемента управления используются следующие элементы:
- Функции рисования, предоставляемые базовым классом System.Windows.Forms.Control.
- Обязательные элементы библиотеки графических объектов GDI.
- Геометрия области рисования.
- Процедура высвобождения графических ресурсов.
Рисование, предоставляемое элементом управления
Класс данных событий для события Paint, PaintEventArgs, содержит данные, необходимые для рисования элемента управления, — маркер для графического объекта и прямоугольник, представляющий область для рисования.
Graphics — это управляемый класс, который инкапсулирует функциональные возможности рисования, как описано в разделе, посвященном GDI, далее в этой статье. ClipRectangle является экземпляром структуры Rectangle и определяет доступную область, в которой можно нарисовать элемент управления. Разработчик элемента управления может вычислить ClipRectangle с помощью свойства ClipRectangle элемента управления, как описано в обсуждении геометрии далее в этой статье.
OnPaint
Элемент управления должен предоставлять логику отрисовки путем переопределения метода OnPaint, который он наследует от Control. OnPaint получает доступ к графическому объекту и прямоугольнику для рисования с помощью свойств Graphics и ClipRectangle экземпляра PaintEventArgs, переданного в него.
В следующем коде используется пространство имен System.Drawing :
Не вызывайте OnPaint непосредственно из элемента управления; вместо этого вызовите метод Invalidate (наследуется от Control) или другой метод, который вызывает Invalidate. Метод Invalidate, в свою очередь, вызывает OnPaint. InvalidateМетод перегружен, и в зависимости от аргументов Invalidate e , перерисовок, перерисовывается часть или вся его область экрана.
Код в методе OnPaint элемента управления будет выполняться при первом рисовании элемента управления и при каждом его обновлении. Чтобы обеспечить перерисовку элемента управления при каждом изменении его размера, добавьте следующую строку в конструктор элемента управления:
OnPaintBackground
Базовый класс Control определяет другой метод, который удобен для рисования, метод OnPaintBackground.
OnPaintBackground рисует фон (и таким образом, фигуру) окна, что гарантированно будет быстрым, в то время как OnPaint закрашивает детали и может быть медленнее, поскольку отдельные запросы рисования объединяются в одно событие Paint, охватывающее все области, которые необходимо перерисовать. Может потребоваться вызвать OnPaintBackground, если, например, нужно нарисовать градиентный фон для элемента управления.
Хотя OnPaintBackground имеет элемент, подобный событиям и принимающий тот же аргумент, что и метод OnPaint , OnPaintBackground не является истинным методом события. События PaintBackground и OnPaintBackground не вызывают делегаты событий. При переопределении метода OnPaintBackground для вызова метода OnPaintBackground своего базового класса не требуется производный класс.
Основы GDI+
Класс Graphics предоставляет методы для рисования различных фигур, таких как круги, треугольники, дуги и эллипсы, а также методы для отображения текста. System.DrawingПространство имен содержит пространства имен и классы, инкапсулирующие графические элементы, такие как фигуры (круги, прямоугольники, дуги и др.), цвета, шрифты, кисти и т. д.
Геометрия области рисования
Свойство ClientRectangle элемента управления указывает прямоугольную область, доступную для элемента управления на экране пользователя, а свойство ClipRectangle объекта PaintEventArgs — закрашиваемую область. Элементу управления может потребоваться закрасить только часть его доступной области, например в случае, когда изменяется небольшой раздел элемента управления. В таких ситуациях разработчик элемента управления должен вычислить фактический прямоугольник для рисования и передать его в Invalidate. Перегруженные версии Invalidate, принимающие Rectangle или Region в качестве аргумента, используют этот аргумент для создания свойства ClipRectangle элемента PaintEventArgs.
Освобождение графических ресурсов
Графические ресурсы являются ресурсоемкими, поскольку они используют системные ресурсы. К таким объектам относятся экземпляры класса System.Drawing.Graphics и экземпляры System.Drawing.Brush, System.Drawing.Pen и других графических классов. Графический ресурс следует создавать только в том случае, когда он вам нужен, и его следует сразу выпустить, как только вы завершите его использование. Если вы создаете экземпляр типа, который реализует интерфейс IDisposable, вызовите его метод Dispose, когда завершите работу с ним, чтобы освободить ресурсы.
Я смотрю на многообразие цветов. Я смотрю на пустой холст. Затем я пытаюсь нанести цвета как слова, из которых возникают поэмы, как ноты, из которых возникает музыка.
Материал предыдущих глав даёт вам всё необходимое для создания простого веб-приложения. Именно этим мы и займёмся.
Наше приложение будет программой для рисования в браузере, схожей с Microsoft Paint. С его помощью можно будет открывать файлы с изображениями, малевать на них мышкой и сохранять обратно. Вот, как это будет выглядеть:
Простая программа рисования
Рисовать на компьютере клёво. Не надо волноваться насчёт материалов, умения, таланта. Просто берёшь, и начинаешь калякать.
Реализация
Интерфейс программы выводит вверху большой элемент , под которым есть несколько полей ввода. Пользователь рисует на картинке, выбирая инструмент из поля , а затем нажимая на холсте мышь. Есть инструменты для рисования линий, стирания кусочков картинки, добавления текста и т.п.
Щелчок на холсте передаёт событие «mousedown» текущему инструменту, который обрабатывает его, как считает нужным. Рисование линий, например, будет слушать события «mousemove», пока кнопка мыши не будет отпущена, и нарисует линию по пути мыши текущим цветом и размером кисти.
Цвет и размер кисти выбираются в дополнительных полях ввода. Они выполняют обновление свойств контекста рисования на холсте fillStyle, strokeStyle, и lineWidth каждый раз при их изменении.
Загрузить картинку в программу можно двумя способами. Первый использует поле file, где пользователь выбирает файл со своего диска. Вторая запрашивает URL и скачивает картинку из интернета.
Картинки хранятся нестандартным способом. Ссылка save с правой стороны ведёт на текущую картинку. По ней можно проходить, делиться ей или сохранять файл через неё. Я скоро объясню, как это работает.
Строим DOM
Интерфейс программы состоит из более чем 30 элементов DOM. Нужно их как-то собрать вместе.
Очевидным форматом для сложных структур DOM является HTML. Но разделять программу на HTML и скрипт неудобно – для элементов DOM понадобится множество обработчиков событий или других необходимых вещей, которые надо будет как-то обрабатывать из скрипта. Для этого придётся делать много вызовов querySelector и им подобных, чтобы найти нужный элемент DOM для работы.
Было бы удобно определять части DOM рядом с теми частями кода JavaScript, которые ими управляют. Поэтому я решил создавать всю конструкцию DOM прямо в JavaScript. Как мы видели в главе 13, встроенный интерфейс для создания структур DOM ужасно многословен. Поскольку нам придётся создать много конструкций, нам понадобится вспомогательная функция.
Эта функция – расширенная версия функции elt из главы 13. Она создаёт элемент с заданным именем и атрибутами, и добавляет все остальные аргументы, которые получает, в качестве дочерних узлов, автоматически преобразовывая строки в текстовые узлы.
Так мы легко и просто создаём элементы, не раздувая код до размеров лицензионного соглашения.
Основание
Ядро нашей программы – функция createPaint, добавляющая интерфейс рисования к элементу DOM, который передаётся в качестве аргумента. Так как мы создаём программу последовательно, мы определяем объект controls, который будет содержать функции для инициализации разных элементов управления под картинкой.
У каждого элемента управления есть доступ к контексту рисования на холсте, а через него – к элементу . Основное состояние программы хранится в этом холсте – он содержит текущую картинку, выбранный цвет (в свойстве fillStyle) и размер кисти (в свойстве lineWidth).
Мы обернём холст и элементы управления в элементы с классами, чтобы можно было добавить им стили, например серую рамку вокруг картинки.
Выбор инструмента
Первый элемент управления, который мы добавим – элемент , позволяющий выбирать инструмент рисования. Как и в случае с controls, мы будем использовать объект для сбора необходимых инструментов, чтобы не надо было описывать их работу в коде по отдельности, и чтобы можно было легко добавлять новые. Этот объект связывает названия инструментов с функцией, которая вызывается при их выборе и при клике на холсте.
В поле tool есть элементы для всех определённых нами инструментов, а обработчик события «mousedown» на холсте берёт на себя обязанность вызывать функцию текущего инструмента, передавая ей объекты event и context. Также он вызывает preventDefault, чтобы зажатие и перетаскивание мыши не вызывало выделения участков страницы.
Самый простой инструмент – линия, который рисует линии за мышью. Чтобы рисовать линию, нам надо сопоставить координаты курсора мыши с координатами точек на холсте. Вскользь упомянутый в 13 главе метод getBoundingClientRect может нам в этом помочь. Он говорит, где показывается элемент, относительно левого верхнего угла экрана. Свойства события мыши clientX и clientY также содержат координаты относительно этого угла, поэтому мы можем вычесть верхний левый угол холста из них и получить позицию относительно этого угла.
Несколько инструментов рисования должны слушать событие «mousemove», пока кнопка мыши нажата. Функция trackDrag регистрирует и убирает событие для данных ситуаций.
У неё два аргумента. Один – функция, которая вызывается при каждом событии «mousemove», а другая – функция, которая вызывается при отпускании кнопки. Каждый аргумент может быть не задан.
Инструмент для рисования линий использует две вспомогательные функции для самого рисования.
Функция сначала устанавливает свойство контекста lineCap в “round”, из-за чего концы нарисованного пути становятся закруглёнными, а не квадратными, как это происходит по умолчанию. Этот трюк обеспечивает непрерывность линий, когда они нарисованы в несколько приёмов. Если рисовать линии большой ширины, вы увидите разрывы в углах линий, если будете использовать установку lineCap по умолчанию.
Затем, по каждому событию «mousemove», которое случается, пока кнопка нажата, рисуется простая линия между старой и новой позициями мыши, с использованием тех значений параметров strokeStyle и lineWidth, которые заданы в данный момент.
Аргумент onEnd просто передаётся дальше, в trackDrag. При обычном вызове третий аргумент передаваться не будет, и при использовании функции он будет содержать undefined, поэтому в конце перетаскивания ничего не произойдёт. Но он поможет нам организовать ещё один инструмент, ластик erase, используя очень небольшое дополнение к коду.
Свойство globalCompositeOperation влияет на то, как операции рисования на холсте меняют цвет пикселей. По умолчанию, значение свойства «source-over», что означает, что цвет, которым рисуют, накладывается поверх существующего. Если цвет непрозрачный, он просто заменит существующий, но если он частично прозрачный, они будут смешаны.
Инструмент “erase” устанавливает globalCompositeOperation в «destination-out», что имеет эффект ластика, и делает пиксели снова прозрачными.
Вот у нас уже есть два инструмента для рисования. Мы можем рисовать чёрные линии в один пиксель шириной (это задано значениями свойств холста strokeStyle и lineWidth по умолчанию), и стирать их. Работающий, хотя и примитивный, прототип программы.
Цвет и размер кисти
Предполагая, что пользователи захотят рисовать не только чёрным цветом и не только одним размером кисти, добавим элементы управления для этих настроек.
В главе 18 я обсуждал разные варианты полей формы. Среди них не было полей для выбора цвета. По традиции у браузеров нет встроенных полей для выбора цвета, но за последнее время в стандарт включили несколько новых типов полей форм. Один из них — . Среди других — «date», «email», «url» и «number». Пока ещё их поддерживают не все. Для тега тип по умолчанию – “text”, и при использовании нового тега, который ещё не поддерживается браузером, браузеры будут обрабатывать его как текстовое поле. Значит, пользователям с браузерами, которые не поддерживают инструмент для выбора цвета, необходимо будет вписывать название цвета вместо того, чтобы выбирать его через удобный элемент управления.
При смене значения поля color значения свойств контекста холста fillStyle и strokeStyle заменяются на новое значение.
Настройка размера кисти работает сходным образом.
Код создаёт варианты размеров кистей из массива, и убеждается в том, что свойство холста lineWidth обновлено при выборе кисти.
Сохранение
Такие URL полезны для разных вещей, как, например, включение небольших картинок прямо в файл стилей. Они также позволяют нам ссылаться на создаваемые файлы на стороне клиента, в браузере, не перемещая их сперва на какой-либо сервер.
У элемента холста есть удобный метод toDataURL, который возвращает URL с данными, содержащий картинку на холсте в виде графического файла. Но нам не следует обновлять ссылку для сохранения при каждом изменении картинки. В случае больших картинок перемещение данных в URL занимает много времени. Вместо этого мы подключаем обновление к ссылке, чтоб она обновляла свой атрибут href каждый раз, когда она получает фокус с клавиатуры или над ней появляется курсор мыши.
Таким образом, линк просто сидит себе тихонечко и указывает на неправильные данные, но как только пользователь приблизится к нему, он волшебным образом обновляет себя так, чтобы указывать на текущую картинку.
Если вы загрузите большую картинку, некоторые браузеры поперхнутся слишком большим URL с данными, который получится в результате. Для маленьких картинок система работает без проблем.
Но здесь мы опять сталкиваемся с деталями реализации песочницы в браузере. Когда картинка грузится с URL с другого домена, если ответ сервера не содержит заголовок, разрешающий использование ресурса с других доменов (см. главу 17), холст будет содержать информацию, которая будет видна пользователю, но не видна скрипту.
Мы могли запросить картинку с приватной информацией (график изменений банковского счёта). Если бы скрипт мог получить к ней доступ, он мог бы шпионить за пользователем.
Для предотвращения таких утечек информации, когда картинка, невидимая скрипту, будет загружена на холст, браузеры пометят его как «испорчен». Пиксельные данные, включая URL с данными, нельзя будет получить с «испорченного» холста. На него можно писать, но с него нельзя читать.
Поэтому нам нужна обработка try/catch в функции update для ссылки сохранения. Когда холст «портится», вызов toDataURL выбросит исключение, являющееся экземпляром SecurityError. В этом случае мы перенаправляем ссылку на ещё один вид URL с протоколом javascript:. Такие ссылки просто выполняют скрипт, стоящий после двоеточия, и наша ссылка покажет предупреждение, сообщающее о проблеме.
Загрузка картинок
Последние два элемента управления используются для загрузки картинок с локального диска и с URL. Нам потребуется вспомогательная функция, которая пробует загрузить картинку с URL и заменить ею содержимое холста.
Нам надо поменять размер холста, чтобы он соответствовал картинке. Почему-то при смене размера холста его контекст забывает все настройки (fillStyle и lineWidth), в связи с чем функция сохраняет их и загружает обратно после обновления размера холста.
Элемент управления для загрузки локального файла использует технику FileReader из главы 18. Кроме используемого там метода readAsText у таких объектов есть метод под названием readAsDataURL – а это то, что нам нужно. Мы загружаем файл, который пользователь выбирает, как URL с данными, и передаём его в loadImageURL для вывода на холст.
Загружать файл с URL ещё проще. Но с текстовым полем мы не знаем, закончил ли пользователь набирать в нём URL, поэтому мы не можем просто слушать события “change”. Вместо этого мы обернём поле в форму и среагируем, когда она будет отправлена – либо по нажатию Enter, либо по нажатию на кнопку load.
Теперь мы определили все элементы управления, требующиеся нашей программе, но нужно добавить ещё несколько инструментов.
Закругляемся
Очень просто можно добавить инструмент для вывода текста, который выводит запрос пользователю, куда он должен ввести текст.
Можно было бы добавить полей для размера текста и шрифта, но для простоты мы всегда используем шрифт sans-serif и размер шрифта, как у текущей кисти. Минимальный размер – 7 пикселей, потому что меньше текст будет нечитаемый.
Ещё один необходимый инструмент для каляк-маляк – “аэрозоль”. Она рисует случайные точки под кистью, пока нажата кнопка мыши, создавая более или менее густые точки в зависимости от скорости движения курсора.
Аэрозоль использует setInterval для выплёвывания цветных точек каждые 25 миллисекунд, пока нажата кнопка мыши. Функция trackDrag используется для того, чтобы currentPos указывала на текущее положение курсора, и для выключения интервала при отпускании кнопки.
Чтобы посчитать, сколько точек нужно нарисовать каждый раз по окончанию интервала, функция подсчитывает размер области текущей кисти и делит его на 30. Для поиска случайного положения под кистью используется функция randomPointInRadius.
Эта функция создаёт точки в квадрате между (-1,-1) и (1,1). Используя теорему Пифагора, она проверяет, лежит ли созданная точка внутри круга с радиусом 1. Когда такая точка находится, она возвращает её координаты, умноженные на радиус.
Цикл нужен для равномерного распределения точек. Проще было бы создавать точки в круге, взяв случайный угол и радиус и вызвав Math.sin и Math.cos для создания точки. Но тогда точки с большей вероятностью появлялись бы ближе к центру круга. Это ограничение можно обойти, но результат будет сложнее, чем предыдущий цикл.
Теперь наша программа для рисования готова. Запустите код и попробуйте.
Упражнения
В программе ещё очень много чего можно улучшить. Давайте добавим ей возможностей.
Прямоугольники
Определите инструмент Rectangle, заполняющий прямоугольник (см. метод fillRect из главы 16) текущим цветом. Прямоугольник должен появляться из той точки, где пользователь нажал кнопку мыши, и до той точки, где он отпустил кнопку. Заметьте, что последнее действие может произойти левее или выше первого.
Как только это заработает, вы заметите, что некомфортно не видеть прямоугольник, когда вы перетаскиваете мышь, чтобы указать его размер. Можете ли вы придумать способ показать прямоугольник во время перетаскивания без фактического рисования на холсте, пока кнопка мыши не будет отпущена?
Если не придумаете, вспомните о стиле position: absolute, который мы обсуждали в главе 13. который можно использовать, чтобы выводить узел поверх остального документа. Свойства pageX и pageY событий мыши можно использовать для точного расположения элемента под мышью, записывая нужные значения в стили left, top, width и height.
Пипетка
Ещё один часто встречающийся инструмент – пипетка, который позволяет щелчком мыши на картинке выбрать цвет, который находится под курсором. Сделайте такой инструмент.
Для его изготовления понадобится доступ к содержимому холста. Метод toDataURL примерно это и делал, но получить информацию о пикселе из URL с данными сложно. Вместо этого мы возьмём метод контекста getImageData, возвращающий прямоугольный кусок картинки в виде объекта со свойствами width, height и data. Свойство data содержит массив чисел от 0 до 255, используя четвёрку чисел для представления красного, зелёного, синего и альфа (непрозрачность) компонентов каждого пикселя.
Этот пример получает числа из одного пикселя холста, один раз, когда тот пуст (все пиксели – прозрачные чёрные), и один раз, когда пиксель окрашен в красный цвет.
Аргументы getImageData показывают начальные координаты прямоугольника x и y, которые нам надо получить, за которыми идут ширина и высота.
Игнорируйте прозрачность в этом упражнении, работайте только с первыми тремя компонентамидля заданного пикселя. Также не волнуйтесь по поводу обновления поля color при выборе цвета. Просто убедитесь, что fillStyle и strokeStyle контекста установлены в цвет, оказавшийся под курсором.
Помните, что эти свойства принимают любой цвет, который понимает CSS, включая запись вида rgb(R, G, B), которую вы видели в главе 15.
Заливка
Это упражнение более сложное, чем предыдущие, и оно потребует разработки нетривиального решения хитрой задачи. Убедитесь, что у вас есть свободное время и терпение перед началом работы, и не отчаивайтесь, если сразу у вас что-то не будет получаться.
Инструмент заливки окрашивает пиксель под курсором мыши и под целой группой пикселей вокруг него, имеющих тот же цвет. Для целей нашего упражнения мы будем считать, что эта группа включает все пиксели, до которых можно добраться от начального, двигаясь по одному пикселю по горизонтали и вертикали (но не по диагонали), не прикасаясь к пикселям, чей цвет отличается от исходного.
Следующая картинка демонстрирует множество пикселей, окрашиваемых, когда инструмент заливки применяется к помеченному пикселю:
Заливка не протекает через диагональные разрывы и не касается пикселей, которых нельзя достичь, даже если они того же цвета, что и исходный.
Вам вновь понадобится getImageData для выяснения цвета пикселя. Скорее всего, удобнее будет получить всю картинку за раз, а потом уже получать данные по пикселям из получившегося массива. Пиксели в массиве организованы схожим образом с решёткой из главы 7, по рядам, только каждый пиксель описывается четырьмя значениями. Первое значение для пикселя с координатами (x,y) находится на позиции (x + y × width) × 4
Включайте в рассмотрение четвёртое число (альфа), потому что нам нужно будет различать чёрные и пустые (прозрачные) пиксели.
Поиск соседних пикселей того же цвета требует пройти по поверхности пикселей вверх, вниз, влево и вправо, пока там находятся пиксели того же цвета. За первый проход всю группу пикселей найти не получится. Вместо этого нужно будет сделать что-то похожее на отслеживание в регулярных выражениях, описанное в главе 9. Когда у вас есть больше одного возможного направления, нужно сохранить все те, по которым вы прямо сейчас не идёте, и просмотреть их позже, по окончанию текущего шага.
У картинки среднего размера много пикселей. Постарайтесь свести работу программы к минимуму, или же она будет работать слишком долго. К примеру, игнорируйте пиксели, которые вы уже обрабатывали.
Рекомендую для окраски отдельных пикселей вызывать fillRect, и хранить какую-то структуру данных, где записано, какие пиксели вы уже обошли.
Для рисования графических примитивов в оконных приложениях используются 4 основных типа объектов:
- точка (Pixel);
- перо (Pen);
- кисть (Brush);
- фон (Background).
Точка
Цвет точки задается с помощью функции
COLORREF SetPixel(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ int X, // x-координата точки
_In_ int Y, // y-координата точки
_In_ COLORREF crColor ); // цвет точки
В случае удачного завершения возвращаемое значение функции дублирует цвет точки, в случае ошибки возвращает -1.
Цвет точки представляет собой 32-битное число, заданное в системе RGB:
Можно также воспользоваться функцией
Значения красного, зеленого и синего используются в диапазоне 0…255.
Перо используется для рисования линий и контуров замкнутых фигур. Цвет пера задается функцией
HPEN CreatePen(
_In_ int fnPenStyle, // стиль пера
_In_ int nWidth, // ширина пера (в пикселях)
_In_ COLORREF crColor ); // цвет пера
Стили пера fnPenStyle могут быть заданы согласно таблице
При успешном завершении функция возвращает дескриптор пера, в случае неудачи — константу NULL .
Кисть
Кисть используется для закрашивания замкнутых объектов. Цвет кисти задается с помощью функции
При успешном завершении функция возвращает дескриптор кисти, в случае неудачи — константу NULL .
Эта же функция используется для задания цвета фона .
Можно заранее создать несколько кистей и перьев, а затем выбирать нужные с помощью функции
HGDIOBJ SelectObject(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ HGDIOBJ hgdiobj ); // дескриптор объекта
Рисование графических примитивов
Перемещение в указанную точку осуществляется функцией:
BOOL MoveToEx(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ int X, // координата x точки
_In_ int Y, // координата y точки
_Out_ LP POINT lpPoint ); // указатель на структуру POINT
Координаты точки x и у определяются в пикселях относительно левого верхнего угла.
В случае успешного выполнения возвращает ненулевое значение.
Структура POINT имеет вид
Рисование отрезков осуществляется функцией:
BOOL LineTo(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ int nXEnd, // координата x конечной точки
_In_ int nYEnd ); // координата y конечной точки
В случае успешного выполнения возвращает ненулевое значение.
Рисование прямоугольника осуществляется функцией:
BOOL Rectangle(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ int nLeftRect, // x-координата верхнего левого угла
_In_ int nTopRect, // y-координата верхнего левого угла
_In_ int nRightRect, // x-координата нижнего правого угла
_In_ int nBottomRect); // координата нижнего правого угла
Рисование прямоугольника начинается из точки, в которую осуществлено перемещение с помощью функции MoveTo() .
В случае успешного выполнения возвращает ненулевое значение.
Рисование эллипса осуществляется функцией:
BOOL Ellipse(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ int nLeftRect, // x-координата верхнего левого угла
_In_ int nTopRect, // y-координата верхнего левого угла
_In_ int nRightRect, // x-координата нижнего правого угла
_In_ int nBottomRect); // координата нижнего правого угла
В случае успешного выполнения возвращает ненулевое значение.
Рисование дуги осуществляется функцией:
BOOL ArcTo(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ int nLeftRect, // x-координата верхнего левого угла
_In_ int nTopRect, // y-координата верхнего левого угла
_In_ int nRightRect, // x-координата нижнего правого угла
_In_ int nBottomRect, // y-координата нижнего правого угла
_In_ int nXRadial1, // x- координата конца первого радиуса
_In_ int nYRadial1, // y- координата конца первого радиуса
_In_ int nXRadial2, // x- координата конца второго радиуса
_In_ int nYRadial2 ); // y- координата конца второго радиуса
В случае успешного выполнения возвращает ненулевое значение.
Вывод текста в окно
Для вывода текста в поле окна используется функция
BOOL TextOut(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ int nXStart, // x-координата начала вывода текста
_In_ int nYStart, // y-координата начала вывода текста
_In_ LPCTSTR lpString, // указатель на строку текста
_In_ int cchString ); // количество символов для вывода
В случае успешного выполнения возвращает ненулевое значение.
Задать цвет фона под буквами можно с помощью функции
COLORREF SetBkColor(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ COLORREF crColor ); // цвет
Задать цвет букв можно с помощью функции
COLORREF SetЕTextColor(
_In_ HDC hdc, // дескриптор контекста устройства
_In_ COLORREF crColor ); // цвет
В случае неудачного завершения эти функции возвращают константу CLR_INVALID=0xFFFF .
Использование графических функций
При обработке вызова BeginPaint() , Windows обновляет фон рабочей области с помощью кисти, заданной в поле hbrBackground структуры WNDCLASS , описанной здесь. Вызов BeginPaint() делает всю рабочую область действительной (не требующей перерисовки) и возвращает описатель контекста устройства. Контекст устройства описывает физическое устройство вывода информации (например, экран) и его драйвер. Описатель контекста устройства необходим для вывода в рабочую область окна текста и графики.
- hwnd – дескриптор окна;
- lpPaint – указатель на структуру PAINTSTRUCT .
Структура PAINTSTRUCT имеет вид
typedef struct tag PAINTSTRUCT <
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32]; > PAINTSTRUCT , * P PAINTSTRUCT ;
- hdc – дескриптор контекста устройства.
- fErase – ненулевое значение стирает фон.
- rcPaint – структура RECT , определяющая верхний левый и нижний правый углы рабочей области.
Функция EndPaint() освобождает описатель контекста устройства, после чего его значение нельзя использовать. Возвращает всегда ненулевое значение.
Получение дескриптора контекста устройства осуществляется вызовом функции:
hWnd – дескриптор окна, для которого используется контекст устройства.
Возвращаемое значение – дескриптор контекста устройства.
освобождает контекст устройства hDC для данного окна hWnd , после чего значение контекста устройства нельзя использовать. Возвращает всегда ненулевое значение.
Здравствуйте, Елена, спасибо за Ваш сайт, много интересного. По поводу графики отмечу, что было бы хорошо, если бы здесь было бы упоминание про двойную буферизацию (это помогает избавиться от эффектов мерцания, если такие наблюдаются при рисовании). Вот часть кода из моей программы.
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
switch (message)
//.
case WM_PAINT :
PaintAll(hwnd);
return 0;
case WM_ERASEBKGND:
return 0;
//.
>
return DefWindowProc (hwnd, message, wparam, lparam);
>
namespace e
extern HWND hpicturebox; // это окно где что-то рисуется
>
void PaintAll( HWND hwnd)
HDC hdc, hcmpdc;
HBITMAP hbmp;
HBRUSH hbrush;
HPEN hpen;
PAINTSTRUCT ps;
RECT rect;
BeginPaint(hwnd, &ps); // здесь hwnd окна на котором расположен hpicturebox
EndPaint(hwnd, &ps);
GetClientRect(e::hpicturebox, &rect);
hdc = GetDC(e::hpicturebox);
hcmpdc = CreateCompatibleDC(hdc);
hbmp = CreateCompatibleBitmap(hdc, rect.right - rect.left, rect.bottom - rect.top);
hbrush = CreateSolidBrush(RGB(255, 255, 255));
hpen = CreatePen(PS_INSIDEFRAME, 1, RGB(255, 255, 255));
BeginPaint(e::hpicturebox, &ps);
SelectObject(hcmpdc, hbmp);
SelectObject(hcmpdc, hbrush);
SelectObject(hcmpdc, hpen);
Rectangle(hcmpdc, rect.left, rect.top, rect.right, rect.bottom);
//
PaintPicturebox(hcmpdc); // //
SetStretchBltMode(hdc, COLORONCOLOR);
BitBlt(hdc, 0, 0, rect.right - rect.left, rect.bottom - rect.top, hcmpdc, 0, 0, SRCCOPY);
EndPaint(e::hpicturebox, &ps);
ReleaseDC(e::hpicturebox, hdc);
DeleteDC(hcmpdc);
DeleteObject(hbmp);
DeleteObject(hbrush);
DeleteObject(hpen);
>
Читайте также: