Использование графики


"Одно изображение стоит тысячи слов", — говорил древнекитайский император Сун; его слова верны и в наши времена. 80% информации мозг человека получает по зрительному каналу, и не удивительно, что программисты стараются придать внешнему виду своих программ максимум привлекательности.
Поэтому в Delphi с самого начала появились развитые средства для работы с графическими возможностями Windows. Этому набору объектов и посвящена данная глава.
Графические инструменты Delphi
Разработчики Delphi уделили большое внимание возможностям работы с деловой графикой: простота и удобство ее использования напрямую сказываются на простоте и удобстве созданных приложений. Вместо дебрей графического интерфейса Windows разработчик получил несколько инструментов, сколь понятных, столь же и мощных.
В стандартном графическом интерфейсе Windows (GDI) основой для рисования служит дескриптор контекста устройства нос и связанные с ним шрифт, перо и кисть. В состав VCL входят объектно-ориентированные надстройки над последними, назначением которых является удобный доступ к свойствам инструментов и прозрачная для пользователя обработка всех их изменений.
Обязательным для любого объекта, связанного с графикой в Delphi, является событие:
property OnChange: TNotifyEvent;
Его обработчик вызывается всякий раз, когда меняются какие-то характеристики объекта, влияющие на его внешний вид.
Класс TFont
Класс инкапсулирует шрифт Windows. В Delphi допускаются только горизонтально расположенные шрифты. В конструкторе объекта по умолчанию принимается шрифт System, цвета ciwindowText и размером 10 пунктов.
Свойства класса приведены в табл. 10.1.
Таблица 10.1. Свойства класса TFont

Установка этих свойств вручную, как правило, не нужна. Если вы хотите изменить шрифт для какого-то компонента, воспользуйтесь компонентом TFontDialog. В нем можно и поменять свойства, и сразу увидеть получившийся результат на тестовой надписи; потом выбранный шрифт присваивается свойству Font нужного компонента:
if FontDialogl.Execute then Editl.Font := FontDialogl.Font;
 Примечание 
Если вы хотите, не закрывая диалог, увидеть результат применения шрифта на вашем тексте, включите опцию fdApplyButton в свойстве Options объекта TFontDialog и напишите для него обработчик события onApply. При этом в диалоговом окне появится кнопка Apply, no нажатии которой (событие OnApply) можно изменить параметры шрифта.
 
Класс ТРеn
Этот класс инкапсулирует свойства пера GDI Windows. В конструкторе по умолчанию создается непрерывное (pssolid) черное перо шириной в один пиксел. Свойства класса приведены в табл. 10.2.
Таблица 10.2. Свойства класса Tpen

К сожалению, пунктирные и штрихпунктирные линии (стили psDash, psDot, psDashDot, psDashDotDot) могут быть установлены только для линий единичной толщины. Более толстые линии должны быть сплошными — такое ограничение существует в Windows.
 Примечание 
Операция pmNotXor подходит для рисования перемещающихся линий или фигур, например, при выделении мышью какой-либо области. Если вы два раза нарисуете одну и ту же фигуру таким пером, то после первого раза она появится, после второго — полностью сотрется.
 
Класс TBrush
Этот класс инкапсулирует свойства кисти — инструмента для заливки областей. Когда создается экземпляр этого класса, первоначально используется белая сплошная (styie=bsSolid) кисть. Свойства класса приведены в табл. 10.3.
Таблица 10.3. Свойства класса TBrush

Шрифт, перо и кисть не могут использоваться самостоятельно. Они являются составными частями специального класса, который и будет сейчас рассмотрен.
Класс TCanvas
Этот класс — сердцевина графической подсистемы Delphi. Он объединяет в себе и "холст" (контекст конкретного устройства GDI), и "рабочие инструменты" (перо, кисть, шрифт), и даже "подмастерьев" (набор функций по рисованию типовых геометрических фигур). Будем называть его канвой.
Канва не является компонентом, но она присутствует в качестве свойства во многих других компонентах, которые должны уметь нарисовать себя и отобразить какую-либо информацию.
Читатели, знакомые с графикой Windows, узнают в TCanvas объектно-ориентированную надстройку над контекстом устройства Windows (Device Context, DC). Дескриптор устройства, над которым "построена" канва, может быть востребован для различных низкоуровневых операций. Он задается свойством:
property Handle: HDC;
Для рисования канва включает в себя шрифт, перо и кисть:
property Font: TFont; property Pen: TPen; property Brush: TBrush;
Кроме того, можно рисовать и поточечно, получив доступ к каждому пикселу. Значение свойства:
property Pixels[X, Y: Integer]: TColor;
соответствует цвету точки с координатами X, Y.
Необходимость отрисовывать каждую точку возникает нередко. Однако, если нужно модифицировать все или хотя бы многие точки изображения, свойство Pixels надо сразу отбросить — настолько оно неэффективно. Гораздо быстрее редактировать изображение при помощи свойства scanLine объекта TBitmap; об этом рассказано ниже.
Канва содержит методы-надстройки над всеми основными функциями рисования GDI Windows и свойства, которые приведены в табл. 10.4 и 10.5. При их рассмотрении имейте в виду, что все геометрические фигуры рисуются текущим пером. Те из них, которые можно закрашивать, закрашиваются с помощью текущей кисти. Кисть и перо при этом имеют текущий цвет.

Метод
procedure Refresh;
сбрасывает текущие шрифт, перо и кисть, заменяя их на стандартные, заимствованные из установок Windows (BLACK PEN, HOLLOW_BRUSH, SYSTEM_FONT).
Предусмотрено два события для пользовательской реакции на изменение канвы:
property OnChange: TNotifyEvent; property OnChanging: TNotifyEvent;
Эти события возникают при изменении свойств и вызове методов TCanvas, меняющих вид канвы (т. е. при любом рисовании. В методе MoveTo, например, они не возникают). Отличие их в том, что событие OnChanging вызывается до начала изменений, а событие OnChange — после их завершения.
Идентификатор (код) растровой операции при копировании прямоугольных блоков содержится в свойстве
property CopyMode: TCopyMode; TCopyMode = Longint;
и определяет правило сочетания пикселов, копируемых на канву, с ее текущим содержимым. При этом можно создавать разные изобразительные эффекты. В Delphi определены следующие константы кодов: cmBiackness, craDstlnvert, cmMergeCopy, cmMergePaint, cmNotSrcCopy, cmNotSrcErase, cmPatCopy, cmPatlnvert, cmPatPaint, cmSrcAnd, cmSrcCopy, cmSrcErase, cmSrdnvert, cmSrcPaint, cmWhiteness.
Все они стандартно определены в Windows, и подробное их описание можно найти в документации по GDI. Значением свойства CopyMode по умолчанию является cmSrcCopy — копирование пикселов источника поверх существующих.
Из возможностей, появившихся в классе TCanvas, следует отметить поддержку рисования кривых (полиномов) Безье. Эта возможность впервые
появилась в API Windows NT. Для построения одной кривой нужны минимум четыре точки — начальная, конечная и две опорные. По ним будет построена кривая второго порядка. Если задан массив точек, они используются для построения последовательных кривых, причем последняя точка одной кривой является первой для следующей кривой.
Хорошей иллюстрацией использования объекта TCanvas может служить пример GraphEx, поставляемый вместе с Delphi (папка \Demos\Doc\GraphEx). Есть только одно "но" — он приводится в неизменном виде, начиная с версии Delphi 1.0. Поэтому сделаем часть работы за программистов Borland. В нашем примере модернизированы Панели инструментов — они выполнены на компонентах TToolBar и TControlBar; добавлена поддержка файлов JPEG; и, наконец, добавлена возможность рисования кривых Безье. Обновленный внешний вид главной формы примера GraphEx показан на 1.
Где же найти ту канву, на которой предстоит рисовать? Во-первых, ею снабжены все потомки классов TGraphicControl и TCustomControl, т. е. почти все визуальные компоненты из Палитры компонентов; в том числе и форма. Во-вторых, канву имеет растровая картинка (класс TBitmap); вы можете писать и рисовать не на пустом месте, а на готовом изображении (об этом см. ниже в разд. "Класс TBitmap" данной главы). Но иногда нужно рисовать и прямо на экране. В этом случае придется прибегнуть к использованию функций API. Функция Getoc возвращает контекст устройства заданного окна, если ей передается параметр 0 — то всего экрана:
ScreenCanvas := TCanvas.Create; 
ScreenCanvas.Handle :=GetDC{0);
 // Рисование на ScreenCanvas
 ReleaseDC(0, ScreenCanvas.Handle);
 ScreenCanvas.Free;
Пример необходимости рисования на экране — программы сохранения экрана (Screen savers).
Когда и где следует рисовать? Этот вопрос далеко не риторический, как может показаться с первого взгляда.
Помимо графических примитивов, таких как линии и фигуры, на канве можно разместить готовые изображения. Для их описания создан класс TGraphic.
 
Класс TGraphic
Канва, перо, кисть и шрифт нужны, чтобы нарисовать свою картинку. Чтобы загрузить готовую, необходимы объекты, "понимающие" графические форматы Windows.
Абстрактный класс TGraphic является родительским для трех видов изображений, общепринятых в графике Windows — значка (компонент Ticon), метафайла (компонент TMetafile) И растровой картинки (компонент TBitmap). Четвертым потомком TGraphic является TuPEGimage — сжатая растровая картинка в формате JPEG.
Работая над приложением в Delphi, вы никогда не будете создавать объекты класса TGraphic, но переменной этого типа вы можете присваивать указатель на любой из перечисленных классов-потомков.
Метод:
procedure Assign(Source: TPersistenti;
переопределяет одноименный метод предка, допуская полиморфное присваивание графических объектов.
Загрузку и выгрузку графики в поток осуществляют методы:
procedure LoadFromStream(Stream: TStream); 
procedure SaveToStream(Stream: TStream);
а загрузку и выгрузку в файл — методы:
procedure LoadFromFile(const Filename: string); procedure SaveToFile(const Filename: string);
Эти методы создают соответствующий файловый поток и затем вызывают методы LoadFromStream/SaveToStream.
Два метода осуществляют взаимодействие с буфером обмена Windows:
procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle; 
APalette: HPALETTE);
procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle; 
var APalette: HPALETTE);
Здесь AFormat — используемый графический формат; AData и APalette — данные и палитра (если она требуется). Потомок должен иметь свой формат представления в буфере обмена и уметь обрабатывать данные, представленные в нем.
Загрузка больших графических файлов может продолжаться очень долго. Чтобы скрасить пользователю ожидание, программист может обработать событие OnProgress!
type
TProgressStage = (psStarting, psRunning, psEnding);
TProgressEvent = procedure (Sender: TObject; Stage: TProgressStage;
PercentDone: Byte; RedrawNow: Boolean; const
R: TRect; const Msg: string) of object; property OnProgress: TProgressEvent;
Оно вызывается графическими объектами во время длительных операций. Параметр stage означает стадию процесса (начало/протекание/завершение), a PercentDone — процент сделанной работы. Сразу оговоримся, что не все из тех объектов, которые будут нами описаны, вызывают обработчик события OnProgress. 
Свойство:
property Empty: Boolean;
устанавливается в значение True, если графический объект пуст (в него не загружены данные).
Высота и ширина графического объекта задаются свойствами:
property Height: Integer; property Width: Integer;
Для каждого дочернего типа эти параметры вычисляются своим способом. Наконец, свойство:
property Modified: Boolean;
показывает, модифицировался ли данный графический объект. Это свойство устанавливается в значение True внутри обработчика события OnChange.
Многие графические объекты при отрисовке должны быть прозрачными. Одни из них прозрачны всегда (значок, метафайл), другие — в зависимости от значения свойства
property Transparent: Boolean;
 
Класс TPicture
Это класс-надстройка над TGraphic, точнее — над его потомками. Он имеет поле Graphic, которое может содержать объекты классов TBitmap, Ticon, TMetafile и TJPEGimage. Предназначение класса TPicture — управлять вызовами соответствующих методов, скрывая при этом хлопоты с определением типа графического объекта и детали его реализации.
Кроме того, на уровне TPicture определены возможности регистрации и использования других — определенных пользователем — классов графических объектов, порожденных от TGraphic. Доступ к графическому объекту осуществляется посредством свойства:
property Graphic: TGraphic;
Если графический объект имеет один из трех предопределенных типов, то к нему можно обратиться и как к одному из свойств:
property Bitmap: TBitmap; property Icon: Ticon;
 property Metafile: TMetafile;
Обращаясь к этим функциям, нужно быть осторожным. Если в поле Graphic хранился объект одного класса, а затребован объект другого класса, то прежний объект уничтожается, а вместо него создается пустой объект требуемого класса. Например:
Imagel.Picture.LoadFromFile('myicon.ico'); //Создан и загружен объект
класса Ticon
MyBitmap := Imagel.Picture.Bitmap; // прежний Ticon уничтожается
Если же вы описали свой класс (допустим, TGiFimage), то к его методам и свойствам следует обращаться так:
(Graphic as TGIFImage).MyProperty := MyValue;
Перечислим остальные методы и свойства.

  •  procedure LoadFromFile(const Filename: string);

Анализирует расширение имени файла FileName и если оно известно (зарегистрировано), то создается объект нужного класса и вызывается его метод LoadFromFile. В противном случае возникает исключительная ситуация EinvaiidGraphic. Стандартными расширениями являются ico, wmf (emf) и bmp. Если подключить к приложению модуль JPEG.PAS, то можно будет загрузить и файлы с расширениями jpg и jpeg.

  •  procedure SaveToFile(const Filename: string);

Сохраняет графику в файле, вызывая соответствующий метод объекта Graphic.

  •  procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle; APalette: HPALETTE);

Во многом аналогичен методу LoadFromFile. Если формат AFormat найден среди зарегистрированных, то AData и APalette передаются для загрузки методу соответствующего объекта. Изначально зарегистрированных форматов три: битовое изображение CF_BITMAP, метафайлы CF_METAFILEPICT И CF_ENHMETAFILE.

  •  procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle; var APalette: HPALETTE);

Сохраняет графику в буфере обмена, вызывая метод объекта Graphic.

  •  procedure Assign(Source: TPersistent);

Метод Assign переписан таким образом, чтобы присваиваемый объект мог принадлежать как классу TPicture, так и TGraphic или любого его потомка. Кроме того, он может быть равен nil — в этом случае поле Graphic очищается с удалением прежнего объекта.

  •  class function SupportsClipboardFormat(AFormat: Word): Boolean;

Метод класса возвращает значение True, если формат AFormat поддерживается классом TPicture (зарегистрирован в системе). Напомним, что методы класса можно вызывать через ссылку на класс без создания экземпляра объекта.

  •  class procedure RegisterFileFormat(const AExtension, ADescription: string; AGraphicClass: TGraphicClass); class procedure RegisterClipboardFormat(AFormat: Word; AGraphicClass: TGraphicClass);

Предназначены для создателей новых графических классов. Они позволяют зарегистрировать формат файла и буфера обмена и связать их с созданным классом — потомком TGraphic, который умеет читать и записывать информацию в этом формате.

  •  property Width: Integer; property Height: Integer;

Ширина и высота картинки. Значения этого свойства всегда те же, что и у объекта из свойства Graphic.
Все три разновидности графических объектов имеют свои системы кэширования. Это означает, что на один реально существующий в системе (и занимающий долю ресурсов!) дескриптор могут одновременно ссылаться несколько объектов. Реализуется такое связывание через метод Assign. Выражение:
Iconl.Assign(Icon2) ;
означает, что два этих объекта разделяют теперь один, фактически находящийся в памяти, значок.
Более простым является кэширование для классов TIсоn и TMetafile, которые умеют только отображать себя и не предназначены для редактирования (создатели Delphi считают, что дескриптор графического объекта дается программисту не для того, чтобы "ковыряться" в нем на уровне двоичных кодов). Гораздо сложнее устроен механизм кэширования для канала TBitmap, который имеет свою канву для рисования.
Внутреннее представление информации в графических объектах двоякое — она может храниться как поток типа TMemoryStream (в него загружается содержимое соответствующего файла), как область памяти с дескриптором (структура которой зависит от типа графического объекта) и одновременно в двух этих видах, содержимое которых автоматически синхронизируется. Поэтому будьте готовы, что загрузка изображения потребует вдвое большего объема памяти — особенно это актуально для больших картинок.
Кого-то может удивить отсутствие объявленных методов рисования, вроде метода Draw для классов Ticon, TMetafile и TBitmap. Объяснение простое — в процессе рисования они играют пассивную роль; рисуют не они — рисуют их. Все рисование должно осуществляться через вызовы методов Draw и stretchDraw канвы, содержащей графику, ибо канва соответствует тому контексту, в котором должна осуществляться операция.
Рассмотрим предопределенные графические классы.
Класс TMetafile
Инкапсулирует свойства метафайла Windows. С появлением Windows 95 к стандартному метафайлу (формат WMF) добавился расширенный (формат EMF), обладающий расширенными возможностями. Соответственно в объекте TMetafile имеется свойство
property Enhanced: Boolean;
Внутреннее представление метафайла всегда новое (EMF), и устанавливать свойство Enhanced в значение False следует только для обеспечения совместимости со старыми программами.
В классе TMetafile перекрываются методы Assign, LoadFromStream, SaveToStream, LoadFromClipboardFormat, SaveToClipboardFormat. В буфер обмена объект помещает свое содержимое в формате CF_ENHMETAFILE. Помимо общих, класс имеет следующие свойства:

  •  дескриптор метафайла

property Handle: HMETAFILE;

  •  свойство property inch: Word.Число точек на дюйм в координатной системе метафайла. Связано с установленным режимом отображения;
  •  свойства

property MMHeight: Integer;
 property MMWidth: Integer;
это настоящие высота и ширина метафайла в единицах, равных 0,01 мм. Свойства Height и width задаются в пикселах;

  •  в метафайл можно добавить свою палитру:

property Palette: HPalette;

  •  вы можете увековечить себя, установив два свойства метафайла:

property Description: string; 
property CreatedBy: string;
Содержащаяся в них информация записывается в файл и может быть прочитана благодарными потомками.

Класс Tlcon


Этот класс инкапсулирует значок Windows.
Не пытайтесь изменить размеры значка — они по определению постоянны (и равны GetSystemMetrics(SM_CXICON) И GetSystemMetrics(SM_CYICON)) , и при попытке присвоить новые значения возникает исключительная ситуация EinvaiidGraphicOperation. Значок нельзя также читать и писать в буфер обмена, т. к. в Windows нет соответствующего формата.
Свойство Transparent для значка всегда равно значению True. Изменить его нельзя — значки прозрачны также по определению.
В этом классе перекрываются методы класса TGraphic: Assign, LoadFromStream и SaveToStream. Дополнительно также определены:

  •  property Handle: HICON; дескриптор значка;
  •  function ReieaseHandie: HICON; — метод "отдает" дескриптор— возвращает его значение, обнуляя ссылку на него в объекте.

Класс TBitmap


Класс TBitmap является основой растровой графики в Delphi. В первых версиях среды этот класс соответствовал битовой карте, зависимой от устройства (Device Dependent Bitmap, DDB). Этот формат хорош для деловой графики — отображения небольших картинок с малой глубиной цвета, например, на кнопках. Формат DDB появился во времена первых версий Windows, когда еще не было графических ускорителей и кое-где еще помнили о EGA. Поэтому и форматы хранения были привязаны к определенным видеорежимам.
Со временем аппаратура совершенствовалась, росло и количество поддерживаемых видеорежимов. Появились режимы High Color (15—16 бит на точку) и True Color (24 бита на точку). Все это привело к тому, что картинка стала храниться в аппаратно-независимом формате (Device Independent Bitmap, DIB), а проблемы ее быстрого отображения легли на аппаратуру и драйверы.
За формат битовой карты — DIB или DDB — отвечает свойство:
type TBitraapHandleType = (bmDIB, bmDDB);
 property HandleType: TBitmapHandleType;
По умолчанию устанавливается режим bmDIB. Впрочем, можно заставить приложение, написанное на Delphi, вернуться к старому типу. Для этого нужно установить глобальную переменную DOBsOnly (модуль GRAPHICS.PAS) в значение True. Впрочем, необходимость этого сомнительна. Все новые видеокарты и драйверы к ним, а также графические интерфейсы (такие, как DirectX) оптимизированы для использования DIB.
Желаемую глубину цвета битовой карты можно узнать и переустановить, меняя значение свойства:
TPixelFormat = (pfDevice, pflbit, pf4bit, pfSbit, pflSbit, pf!6bit, pf24bit, pf32bit, pfCustom); 
property PixelFormat: TPixelFormat;
Режим pfDevice соответствует битовой карте DDB. Глубина цвета в 1, 4 и 8 бит на пиксел — традиционная и предусматривает наличие у изображения палитры. Другие режимы заботятся о хранении непосредственных яркостей точек в каждом из трех основных цветов — красном (R), зеленом (G) и синем (В). Разрядность 15 бит соответствует распределению бит 5-5-5 (RGB555), 16 бит - RGB 565, 24 бит - RGB888. Режим 32 бит похож на 24-битный, но в нем дополнительно добавлен четвертый канат (альфа-канал), содержащий дополнительную информацию о прозрачности каждой точки. Режим pfCustom предназначен для реализации программистом собственных графических конструкций. В стандартном классе TBitmap установка свойства PixelFormat в режим pfCustom приведет к ошибке — поэтому использовать его нужно только в написанных вами потомках TBitmap.
Битовая карта является одним из видов ресурсов. Естественно, что класс TBitmap поддерживает загрузку из ресурсов приложения:
procedure LoadFromResourcelD(Instance: THandle; ResID: Integer);
 procedure LoadFromResourceName(Instance: THandle; const ResName: string);
Здесь instance — это глобальная переменная модуля System, хранящая уникальный идентификатор запущенной копии приложения (или динамической библиотеки).
Канва битовой карты доступна через свойство:
property Canvas: TCanvas;
С ее помощью можно рисовать на поверхности растрового изображения. Обратите внимание, что никакие другие потомки TGraphic канвы не имеют.
Дескрипторы битовой карты и ее палитры доступны как свойства:
property Handle: HBITMAP; 
property Palette: HPALETTE;
Имея дело с классом TBitmap, учитывайте, что принцип "один объект — один дескриптор" из-за наличия механизма кэширования неверен. Два метода:
function ReleaseHandle: HBITMAP; 
function ReleasePalette: HPALETTE;
возвращают дескрипторы битовой карты и палитры соответственно, а после этого обнуляют дескрипторы, т. е. как бы "отдают" их пользователю.
При любом внешнем обращении к дескриптору битовой карты и любой попытке рисовать на ее канве разделение одной картинки несколькими объектами прерывается, и объект получает собственную копию содержимого дескриптора. Для этого есть методы:

  •  procedure Dormant — выгружает изображение в поток и уничтожает дескрипторы битовой карты и палитры;
  •  procedure Freeimage — "освобождающий" дескриптор битовой карты для дальнейшего использования и внесения изменений. Это означает, что если на данный дескриптор есть ссылки, то он дублируется; поток очищается.

Битовая карта может быть монохромной и цветной, что определено свойством:
property Monochrome: Boolean;
Значение True соответствует монохромной битовой карте. При его изменении происходит преобразование содержимого к требуемому виду.
За прозрачность битовой карты отвечают следующие свойства:
property TransparentColor: TColor;
type TTransparentMode = (tmAuto, tmFixed);
property TransparentMode: TTransparentMode;
Если свойство TransparentMode установлено в режим tmAuto, то за прозрачный (фоновый) принимается цвет верхнего левого пиксела. В противном случае этот цвет берется из свойства Transparentcolor.
Битовая карта может использоваться в качестве маски для других битовых карт. В этом случае она превращается в двухцветную, где в белый цвет окрашиваются точки фона (см. свойство Transparentcolor), а в черный — все остальные. Для поддержки этого режима служат следующие методы и свойства:
procedure Mask(Transparentcoior: TColor); 
property MaskHandle: HBitmap; 
function ReleaseMaskHandle: HBitmap;
Наконец, последним по счету будет рассмотрено очень важное свойство битовой карты TBitmap. Если формат ее хранения — DIB, то есть возможность получить доступ к данным самой битовой карты:
property ScanLine[Row: Integer]: Pointer;
Это свойство представляет собой массив указателей на строки с данными битовой карты. Параметр ROW содержит номер строки. Следует помнить, что в большинстве случаев строки в битовой карте упорядочены в памяти снизу вверх и фактически первой после заголовка хранится нижняя строка. Код, возвращающий значение свойства ScanLine, это учитывает; поэтому не удивляйтесь, если с ростом параметра ROW значение свойства уменьшается.
Внутри строки данные упорядочены в соответствии с форматом (pixelFormat). Для формата pfsbit все просто — каждый байт в строке соответствует одному пикселу. Для форматов pfisbit и pfiebit пикселу соответствуют два байта (в этих 16 битах упакованы данные о трех каналах), pf24bit — три байта (по байту на канал).
Примерно так может выглядеть обработчик события onMouseMove, выводящий на панель состояния информацию о яркости в данной точке (подразумевается, что формат битовой карты — 8 или 24 бита):
procedure TMainForm.ImagelMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
begin
if not Assigned(Imagel. Picture.Bitmap) then Exit;
with Imagel.Picture.Bitmap, 
do case PixelFormat of
pfSbit: Statusbarl.SimpleText := Format('x: %d y: %d b: %d',[x, y, pByteArray(ScanLine[у])^[x] ]);
pf24bit: Statusbarl.SimpleText := Format('x: %d y: %d R: %d,G: %d, B: %d', 
[x,y, pByteArray(ScanLine[y])л[3*х], pByteArray(ScanLine[у])^[ 3*x+l], pByteArray(ScanLine[у])^[ 3*х+2]]); 
end;
Само значение свойства ScanLine изменить нельзя (оно доступно только для чтения). Но можно изменить данные, на которые оно указывает. Вот так можно получить негатив 24-битной картинки:
Var line : pByteArray;
For i:=0 to Imagel.Picture.Bitmap.Height — 1 do
 Begin
Line := Imagel.Picture.Bitmap.ScanLine[i];
 For j:=0 to Imagel.Picture.Bitmap.Width * 3 - 1 do
 Line^[j] := 255 - Line^[j];
 End;
Если вы хотите решать более серьезные задачи — на уровне профессиональных средств — на помощь может прийти библиотека обработки изображений фирмы Intel (Intel Image Processing Library). Этот набор инструментов позволяет разработчику включать в программы алгоритмы обработки изображений, написанные и оптимизированные специально для процессоров фирмы Intel. Библиотека является свободно распространяемой, и последняя ее версия располагается на Web-сайте фирмы. Интерфейсы к функциям библиотеки для Delphi разработаны авторами этой книги и вместе с примерами находятся на прилагаемой к книге дискете.
 Примечание 
В Delphi можно столкнуться с "тезкой" рассматриваемого объекта — структурой TBitmap, описанной в файле WINDOWS.PAS. Поскольку обе они относятся к одной и той же предметной области, часто возникают коллизии, приводящие к ошибкам. Напомним, чтобы отличить структуры-синонимы, следует использовать имя модуля, в котором они описаны. Поэтому если в вашей программе есть модули Windows и Graphics, то описывайте и употребляйте типы Windows.TBitmap И Graphics.TBitmap.
В состав Windows входят карточные игры (точнее, пасьянсы), которые черпают ресурсы из динамической библиотеки cards.dll. Если вы откроете эту библиотеку в редакторе ресурсов, то увидите там изображения всех пятидесяти двух карт и десятка вариантов их рубашек (оборотных сторон). Используем эту возможность для рисования карт. Так загружается битовая карта для рубашки:
var CardsDll : THandle;
BackBitmap : Graphics.TBitmap; 
initialization
CardsDll := LoadLibraryEx('cards.dll',0, LOAD_LIBRARY__AS_DATAFILE) ;
BackBitmap := Graphics.TBitmap.Create;
BackBitmap.LoadFromResourcelD(CardsDll, 64) ;
finalization
BackBitmap.Free;
FreeLibrary(CardsDll); 
end.
 Примечание
В Windows 95/98 эта динамическая библиотека — 16-разрядная, и работать так, как описано, не будет. Используйте библиотеку Cards.dll из состава Windows NT, 2000.
Аналогичным образом можно загрузить битовые карты для всей колоды. При показе карты, в зависимости от того, открыта она или закрыта, отрисовывается один из объектов TBitmap:
if Known then // карта открыта
Canvas.StretchDraw(ClientRect, FaceBitmap) 
else
Canvas.StretchDraw(ClientRect,BackBitmap) 
end;
 
Графический формат JPEG. Класс TJPEGImage
В 1988 году был принят первый международный стандарт сжатия неподвижных изображений. Он был назван по имени группы, которая над ним работала — JPEG (Joint Photographic Expert Group). Дело в том, что стандартные архиваторы (ZIP, ARJ) и традиционные алгоритмы сжатия в форматах GIF, TIFF и PCX не могут достаточно сильно сжать полутоновую или цветную картинку (типа фотографии) — максимум в 2—3 раза. Примененный в JPEG алгоритм позволяет достичь сжатия в десятки раз — правда, при этом изображение подвергается необратимому искажению, и из него пропадает часть деталей. Бессмысленно (и вредно!) подвергать хранению в формате JPEG чертежи, рисунки, а также любые изображения с малым числом градаций — он предназначен именно для изображений фотографического качества.
Поддержка формата JPEG реализована в Delphi посредством класса TJPEGImage, который является потомком класса TGraphic.
Примечание
Название TJPEGImage не совсем удачное. К Timage этот класс не имеет ни малейшего отношения. Скорее, это "двоюродный брат" класса TBitmap.
К такому объекту предъявляются двоякие требования. С одной стороны, он должен поддерживать сжатие данных для записи на диск. С другой — распакованные данные в формате DIB, чтобы по требованию системы отрисовать их. Поэтому объект класса TJPEGimage может хранить оба вида данных, а также производить их взаимные преобразования, т. е. сжатие и распаковку. Для этого в нем предусмотрены методы:
procedure Compress; 
procedure DIBNeeded;
procedure JPEGNeeded;
Рекомендуется вызывать метод DIBNeeded заранее, перед отрисовкой картинки — это ускорит процесс ее вывода на экран.
Кроме того, полезно использовать метод Assign, который позволяет поместить в класс TJPEGimage объект TBitmap и наоборот:
MyJPEGImage.Assign(MyBitmap);
 MyBitmap.Assign(MyJPEGImage);
При этом происходит преобразование форматов.
Свойства TJPEGimage можно условно разделить на две группы: используемые при сжатии и при распаковке.
Важнейшим из свойств, нужных при сжатии, является compressionQuality:
type TJPEGQualityRange = 1..100;
property CompressionQuaiity: TJPEGQualityRange;
Оно определяет качество сжимаемого изображения и его размер. При малых значениях этого свойства файлы получаются очень маленькими, но с большими искажениями (напомним, что стандарт JPEG предусматривает сжатие с потерями качества). При значениях, близких к 100, потери незаметны, но и размер файла при этом максимален.
Примечание 
Заранее предсказать размер сжатого файла нельзя — разные картинки сжимаются по-разному, даже при одном значении CompressionQuality.
По умолчанию значение этого свойства равно 75, что обеспечивает разумный компромисс между размером и качеством.
Кроме CompressionQuality, на качество отображения может повлиять и свойство
type TJPEGPerformance = (jpBestQuality, jpBestSpeed); 
property Performance: TJPEGPerformance;
Оно нужно только при распаковке и отвечает за способ восстановления цветовой палитры из сжатой информации. На качество записываемого изображения оно никак не влияет.
Как и у класса TBitmap, у TJPEGimage есть свойство
type TJPEGPixelFormat = (jf24Bit, jfSBit); 
property PixelFormat: TJPEGPixelForm;
Для рассматриваемого объекта возможных значений всего два— jf8bit и jf24bit. По умолчанию используется 24-битный формат. Если информация о цвете не нужна, то можно установить свойство Grayscale в значение True — в этом случае изображение будет записано (или распаковано) в полутоновом виде (256 оттенков серого).
Свойства ProgressiveEncoding и ProgressiveDisplay определяют способ показа изображения при распаковке. Первое из них отвечает за порядок записи в файл сжатых компонентов. Если ProgressiveEncoding установлено в значение True, начинает играть роль свойство ProgressiveDisplay. От его значения зависит, будет ли показываться изображение по мере распаковки (при значении True), либо будет сначала полностью распаковано, а потом показано (при значении False).
Чтобы организовать предварительный просмотр большого числа больших изображений, уместно воспользоваться свойством:
type TJPEGScale = (jsFullSize, jsHalf, jsQuarter, jsEighth);
 property Scale: TJPEGScale;
Искушенные в графике специалисты зададут вопрос: зачем оно? Ведь можно прочитать изображение, а затем уменьшить его масштаб стандартными способами? Представление информации в файлах JPEG таково, что можно достаточно просто извлечь изображение сразу в нужном масштабе. Таким образом достигается двойной выигрыш — на времени распаковки и на времени отображения.
Примечание 
Печать растровых изображений может вызвать проблемы при согласовании его размеров с размерами листа принтера и его разрешением. Большую часть из них можно снять, изучив пример, поставляемый с Delphi — jpegProj. Он находится не в папке \Demos, как обычно, а в папке Help\Examples\Jpeg.
В заключение отметим, что класс TJPEGimage не имеет своей канвы для рисования — для этого его нужно преобразовать в классе TBitmap.
 
Компонент TImage
Этот компонент служит надстройкой над классом TPicture и замыкает всю иерархию графических объектов VCL. Именно на его поверхности и будут отображаться графические объекты, содержащиеся в свойстве:
property Picture: TPicture;
В качестве канвы компонента (свойство canvas) используется канва объекта из свойства Picture.Graphic, но только если поле Graphic ссылается на объект класса TBitmap. Если это не так, то попытка обращения к свойству вызовет исключительную ситуацию EinvaiidOperation, т. к. рисовать на метафайле или значке нельзя.
Следующие три свойства определяют, как именно графический объект располагается в клиентской области компонента:

  •  property AutoSize: Boolean;

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

  •  property Stretch: Boolean;

Если это свойство установлено в значение True, то изображение "натягивается" на клиентскую область, при необходимости уменьшая или увеличивая свои размеры. Если оно установлено в False, то играет роль следующее свойство Center.

  •  property Center: Boolean;

Если это свойство установлено в значение True, изображение центрируется в пределах клиентской области. В противном случае оно располагается в ее верхнем левом углу.
Несмотря на то, что описанию свойств и методов графических объектов здесь отведено уже довольно много места, работа с ними проста и удобна. Программу для просмотра изображений в среде Delphi можно создать буквально "в три счета":
1. Поместите на форму следующие компоненты: область прокрутки TScrollBox, на нее — компонент Timage (их верхние левые углы должны совпадать), любую кнопку (например, TButton) и диалог открытия файлов TOpenPictureDialog.
2. Подключите к главному модулю создаваемого приложения модуль JPEG (в предложении uses); свойство AutoSize компонента Timage установите в значение True.
3. Дважды щелкните мышью на кнопке. В появившемся обработчике события oncдick напишите такой код:
procedure TForml.BitBtnlClick(Sender: TObject);
 begin
OpenPictureDialogl.Filter := GraphicFilter(TGraphic);
if OpenPictureDialogl.Execute
then Imagel.Picture.LoadFromFile
(OpenPictureDialogl.FileName);
 end;
Приложение готово. Обратите внимание на роль полиморфизма в методе LoadFromFile — по расширению файла определяется его формат и в зависимости от этого создается нужный графический объект.
 
Использование диалогов для загрузки и сохранения графических файлов
Для удобства открытия картинок существует пара компонентов-диалогов: TOpenPictureDialog и TSavePictureDialog.
Список форматов открываемых файлов определяется свойством Filter. Можно, как в случае со стандартными диалогами TOpenDiaiog или TSaveDialog, сформировать их вручную с помощью редактора свойства Filter. Можно поступить проще, воспользовавшись готовыми средствами. Для удобства формирования строк графических фильтров существуют три специальные функции:

  •  function GraphicFilter(GraphicClass: TGraphicClass): string;

Формирует строку с полным текстом графического фильтра, позволяющего открывать все файлы, форматы которых являются потомками параметра GraphicClass. Если в качестве параметра этой функции будет передан класс TGraphic, то в строке будут перечислены все форматы:
'Аll (*.jpg;*.jpeg;*.bmp;*.ico;*.emf;*.wmf} I *.jpg;*.jpeg;*.bmp;*.ico; *.emf;*.wmfIJPEG Image File (*.jpg) I *.jpgI JPEG Image File (*.jpeg) I *.jpeg|Bitmaps (*.bmp) I *.bmplIcons (*.ico) I*.ico|Enhanced Metafiles (*.emf) |*.emf[Metafiles (*.wmf) I *.wmf'
Формат JPEG появляется в перечне, если в приложении используется модуль с тем же названием — JPEG. В приводимом ниже примере возникла необходимость совместить фильтры только для классов TBitmap и TJPEGimage, которые не являются предками друг друга. В этом случае получившиеся строки нужно соединить, использовав символ конкатенации "|":
S := GraphicFilter(TBitmap)+'|'+
GraphicFilter(TJpeglmage) 

  •  function GraphicExtension(GraphicClass: TGraphicClass): string;

Возвращает расширение файла, формат которого соответствует графическому классу GraphicClass. Так, если передать в качестве параметра класс TBitmap, то функция вернет строку 'BMP';

  •  function GraphicFileMask(GraphicClass: TGraphicClass): string;

Эта функция возвращает перечень расширений файлов с форматами — потомками GraphicClass, перечисленных через точку с запятой.
Для диалогов предусмотрено несколько событий, которые программист может обработать самостоятельно. Первые три — достаточно тривиальные: OnShow, oncanciose и enclose. Нужно предостеречь программиста: по чьему-то недосмотру последние два вызываются только в случае нормального завершения диалога (нажатием кнопки Open или Save), а если завершить диалог нажатием кнопки Cancel или "крестика" на заголовке диалога, то управления они не получат.
Другие три события связаны с изменениями, которые осуществляет пользователь во время выбора нужного файла. Они происходят в момент изменения формата открываемого файла (событие onTypeChange), изменения текущей папки (OnFolderChange) и текущего файла (OnSelectionChange).
Но разработчики диалогов не предусмотрели одну очень нужную возможность. Дело в том, что у разных графических форматов возможны различные опции и варианты. Если вы имеете опыт работы с графическими пакетами вроде Adobe Photoshop или Corel, то знаете, что, в зависимости от выбранного формата сохранения данных, диалог изменяет свой внешний вид — к нему добавляются элементы управления, соответствующие параметрам формата.
Поступим так и мы, предусмотрев настройку при сохранении файлов формата JPEG. Для этого будет использовано событие OnTypeChange компонента TSavePictureDialog. Для события нужно проверить значение свойства Filterindex. Если оно больше 1 (т. е. выбраны файлы формата JPEG), нужно увеличить высоту окна диалога и разместить на нем дополнительные компоненты: флажок, соответствующий свойству ProgressiveEncoding, и редактор свойства compressionQuaiity (2). Если тип файла снова поменялся и стал равным 1, нужно эти компоненты убрать.
Поможет нам в этом внимательное изучение исходных кодов диалогов, находящихся в модуле EXTDLGS.PAS. Программисты Borland пошли по пути модернизации внешнего вида стандартных диалогов, добавив к ним справа панель для отображения внешнего вида открываемых (записываемых) картинок. Можно пойти дальше и добавить таким же образом и свои элементы управления.
Приводимый ниже пример ModifDlg — усовершенствованная программа просмотра и сохранения файлов растровой графики, к которым относятся файлы форматов JPEG и BMP. Чтобы исключить метафайлы и значки (*.wmf, *.emf, *.ico), соответствующим образом настраиваются фильтры в диалогах открытия и сохранения.
Для изменения размеров диалогового окна нужно отыскать среди входящих в его состав компонентов панель picturePanel (так назвали ее разработчики Borland) и увеличить ее высоту. Следует также поменять и размеры родительских окон. Поскольку они не являются компонентами Delphi (стандартные диалоги являются составными частями Windows) для этой цели используются функции API GetWindowRect И SetWindowPos.
Обратите также внимание, что при загрузке используется событие OnProgress класса TGraphic. В его обработчике информация об объеме проделанной работы отображается на компоненте progressBar1. Для маленьких картинок обработчик вызывается только в начале и в конце операции, пользователь ничего не заметит. Зато при загрузке большого изображения он будет спокоен, видя, что процесс загрузки идет и машина не зависла.
Листинг 10.1. Исходный текст главного модуля программы ModifDlg 
unit mainUnit; 
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
 Dialogs,
ExtDlgs, StdCtrls, ComCtrls, ExtCtrls, Buttons; 
type
TForml = class(TForm)
SavePictureDialogl: TSavePictureDialog; 
OpenPictureDialogl: TOpenPictureDialog;
 ScrollBoxl: TScrollBox; Imagel: TImage; 
ProgressBari: TProgressBar;
 OpenBitBtn: TBitBtn;
 SaveBitBtn: TBitBtn;
procedure SavePictureDialoglTypeChange(Sender: TObject); 
procedure ImagelProgress(Sender: TObject; Stage: TProgressStage;
 PercentDone: Byte; 
RedrawNow: Boolean; 
const R: TRect;
 const Msg: String);
procedure SavePictureDialoglClose(Sender: TObject); 
procedure FormCreate(Sender: TObject);
procedure SavePictureDialoglShow(Sender: TObject);
 procedure OpenBitBtnClick(Sender: TObject);
 procedure SaveBitBtnClick(Sender: TObject); 
private 
public 
end;
var
Forml: TForml;
implementation
{$R *.DFM} 
uses jpeg;
const DeltaH : Integer = 80; 
var Quality : TJpegQualityRange; 
ProgressiveEnc : Boolean;
procedure TForml.OpenBitBtnClick(Sender: TObject);
begin
if OpenPictureDialogl.Execute
then Imagel.Picture.LoadFromFile
(OpenPictureDialogl.FileName); 
end;
procedure TForml.SaveBitBtnClick(Sender: TObject);
var ji : TJpeglmage;
begin
if SavePictureDialogl.Execute then
begin
ji := TJpeglmage.Create;
ji.CompressionQuality := Quality;
ji.ProgressiveEncoding := ProgressiveEnc;
j i.Assign(Imagel.Picture.Bitmap);
ji.SaveToFile(SavePictureDialogl.FileName);
ji.Free;
end;
 end;
procedure TForml.SavePictureDialoglTypeChange(Sender: TObject);
 var ParentHandle:THandle;wRect:TRect;
PicPanel,PaintPanel:TPanel;JEdit : TEdit; 
Expanded : boolean;
begin
With Sender as TSavePictureDialog do
 begin
PicPanel := (FindComponent('PicturePanel') as TPanel);
if not Assigned(PicPanel) then Exit;
ParentHandle:=GetParent(Handle);
PaintPanel:=(FindComponent('PaintPanel') as TPanel); 
PaintPanel.Align := alNone;
 Expanded := FindComponent('JLabel') <> nil;
 if Filterlndex >1 then begin if not Expanded then
begin
GetWindowRect(ParentHandle,WRect);
SetWindowPos(ParentHandle,0,0,0,
WRect.Right-WRect.Left,
WRect.Bottom-WRect.Top+DeltaH,
SWP_NOMOVE+SWP_NOZORDER);
GetWindowRect(Handle,WRect);
SetWindowPos(handle,0,0,0,WRect.Right-
WRect.Left,
WRect.Bottom-WRect.Top+DeltaH,
SWP_NOMOVE+SWP_NOZORDER);
Expanded:=True;
PicPanel.Height := PicPanel.Height+DeltaH;

if FindComponent('JLabel')=nil
 then with TLabel.Create(Sender as TSavePictureDialog) do
 begin
Parent := PicPanel;
Name := 'JLabel';
Caption := 'Quality';
Left := 5;
Height := 25;
Top := PaintPanel.Top+PaintPanel.Height+5; end;
if FindComponent('JEdit')=nil then 
begin
JEdit := TEdit.Create(Sender as TSavePictureDialog);
 with JEdit do 
begin
Parent := PicPanel;
Name:='JEdit';
Text := '75';
Left:-50;Width := 50;
Height := 25;
Top := PaintPanel.Top+PaintPanel.Height+5; 
end;
 end;
if FindComponent('JUpDown')=nil then 
with TUpDown.Create(Sender as TSavePictureDialog) do
 begin
Parent := PicPanel;
Name:='JUpDown';
Associate := JEdit;
Increment := 5;
Min := 1; Max := 100;
Position := 75; end;
if FindComponent('JCheck')=nil then
with TCheckBox.Create(Sender as TSavePictureDialog) do
 begin
Name: = 'JCheck';
Caption:='Progressive Encoding'; Parent:=PicPanel;
Left:=5;Width := PicPanel.Width - 10; Height:=25;
Top := PaintPanel.Top+PaintPanel.Height+35; 
end;
 end; 
end 
else
SavePictureDialoglClose(Sender);
 end; 
end;
procedure TForml.ImagelProgress(Sender: TObject; Stage: TProgressStage;
 PercentDone: Byte; RedrawNow: Boolean; const R: TRect; 
const Msg: String); 
begin
case Stage of psStarting: 
begin
Progressbarl.Position := 0;
 Progressbarl.Max := 100;
 end;
 psEnding: begin
Progressbarl.Position := 0; 
end;
psRunning: begin
Progressbarl.Position := PercentDone;
end; 
end;
 end;
procedure TForml.SavePictureDialoglClose(Sender: TObject);
 var PicPanel : TPanel; ParentHandle : THandle; WRect : TRect;
 begin
With Sender as TSavePictureDialog do
 begin
PicPanel := (FindComponent('PicturePanel') as TPanel);
 if not Assigned(PicPanel) then Exit; ParentHandle:=GetParent(Handle);
 if ParentHandle=0 then Exit;
 if FindComponent('JLabel')onil then 
begin
FindComponent('JLabel').Free; 
FindComponent('JEdit').Free;
ProgressiveEnc := (FindComponent('JCheck1) as TCheckBox).Checked; FindComponent('JCheck').Free;
Quality := (FindComponent('JUpDown') as TUpDown).Position; FindComponent('JUpDown').Free;
PicPanel.Height:=PicPanel.Height-DeltaH; 
GetWindowRect(Handle,WRect);
SetWindowPos(Handle,0,0,0,WRect.Right-WRect.Left, WRect.Bottom-WRect.Top-DeltaH,
SWP_NOMOVE+SWP_NOZORDER); GetWindowRect(ParentHandle,WRect);
SetWindowPos(ParentHandle,0,0,0,
WRect.Right-WRect.Left, WRect.Bottom-WRect.Top-DeltaH,
SWP_NOMOVE+SWP_NOZORDER); Filterlndex := 1;
 end; 
end; 
end;
procedure TForml.FormCreate(Sender: TObject);
var s: string;
begin
s :=GraphicFilter(TBitmap)+'|'+
GraphicFilter(TJpeglmage);
OpenPictureDialogl.Filter := s;
SavePictureDialogl.Filter := s; 
end;
procedure TForml.SavePictureDialoglShow(Sender: TObject); 
begin
with Sender as TSavePictureDialog do
 begin
if FindComponent('JLabel')Onil then
 begin
FilterIndex := 2;
SavePictureDialoglTypeChange(Sender) ;
 end;
 end; 
end;
end.
Приведенный пример может послужить толчком, во-первых, к углубленному изучению формата JPEG, а во-вторых, — к модификации стандартных диалогов. На его базе можно создать диалоги открытия аудиозаписей, документов и других специализированных видов файлов.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика