Глава 8

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

"Одно изображение стоит тысячи слов" — говорил древнекитайский император Сун; его слова верны и в наши времена. 80% информации мозг человека получает по зрительному каналу, и не удивительно, что программисты стараются придать внешнему виду своих программ максимум привлекательности.

Поэтому в Delphi с самого начала появились развитые средства для работы с графическими возможностями Windows. Этому набору объектов и посвящена данная глава.

Графические инструменты Delphi

Разработчики Delphi уделили большое внимание возможностям работы с деловой графикой: простота и удобство ее использования напрямую сказываются на простоте и удобстве созданных приложений. Вместо дебрей графического интерфейса Windows разработчик получил несколько инструментов, сколь понятных, столь же и мощных.

В стандартном графическом интерфейсе Windows (GDI) основой для рисования служит дескриптор контекста устройства hDC и связанные с ним шрифт, перо и кисть. В состав VCL входят объектно-ориентированные надстройки над последними, назначением которых является удобный доступ к свойствам инструментов и прозрачная для пользователя обработка всех их изменений.

Обязательным для любого объекта, связанного с графикой в Delphi, является событие:

property OnChange: TNotifyEvent;

Его обработчик вызывается всякий раз, когда меняются какие-то характеристики объекта, влияющие на его внешний вид.

Класс TFont

Класс инкапсулирует шрифт Windows. В Delphi допускаются только горизонтально расположенные шрифты. В конструкторе объекта по умолчанию принимается шрифт System цвета clWindowText и размером 10 пунктов.

Свойства класса приведены в табл. 8.1.

Таблица 8.1. Свойства класса TFont

Свойство

Описание

property Handle: Hfont;

Содержит дескриптор шрифта

property Name: TfontName; 

Содержит имя (начертание) шрифта, например, Arial

property Style: TfontStyles;

TFontStyle = (fsBold, fsltalic, fsUnderline,

fsStrikeOut) ;

TFontStyles = set of TFontStyle;

Содержит стиль (особенности начертания) шрифта: соответственно, жирный, курсив, подчеркнутый и перечеркнутый

 

 

property Color: Tcolor;

TColor = -(COLOR ENDCOLORS + 1) ..$2FFFFFF;

Определяет цвет шрифта

property Charset: TftontCharset TFontCharset = 0..255;

Содержит номер набора символов шрифта. По умолчанию равно 1 (default_charset). Для вывода символов кириллицы требуется RUSSIAN_CHARSET

property Pitch: TFontPitch ;

TFontPitch = (fpDefault,

fpVariable, fpFixed) ;

Определяет способ установки ширины символов шрифта. Значение fpFixed соответствует моноширинным шрифтам; fpVariable — шрифтам с переменной шириной символа. Установка fpDefault означает принятие того способа, ко торый определен начертанием

property Height: Integer;

Содержит значение высоты шрифта в пикселах

property PixelsPerInch: Integer;

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

property Size: Integer; 

 

 

 

Содержит размер шрифта в пунктах (как принято в Windows). Это свойство связано с Height соотношением:

Font.Size := -Font.Height*72;

Font.PixelsPerInch

Установка этих свойств вручную, как правило, не нужна. Если вы хотите изменить шрифт для какого-то компонента, воспользуйтесь компонентом TFontDialog. В нем можно и поменять свойства, и сразу увидеть получившийся результат на тестовой надписи; потом выбранный шрифт присваивается свойству Font нужного компонента:

if FontDialogl.Execute then Editl.Font := FontDialogl.Font;

Если вы хотите, не закрывая диалог, увидеть результат применения шрифта на вашем тексте, включите опцию fdApplyButton в свойстве Options объекта TFontDialog и напишите для него обработчик события OnApply. При этом в диалоговом окне появится кнопка Apply, по нажатию которой (событие OnApply) можно изменить параметры шрифта.

 Класс ТРеп

Этот класс инкапсулирует свойства пера GDI Windows. В конструкторе по умолчанию создается непрерывное (pssolid) черное перо шириной в один пиксел. Свойства класса приведены в табл. 8.2.

Таблица 8.2. Свойства класса Tpеп

Свойство

Описание

property Handle: HPen;

Содержит дескриптор пера

property Color: TColor ;

Определяет цвет пера

property Mode: TPenMode ;

TPenMode = (pmBlack, pmWhite,

pmNop, pmNot, pmCopy, pmNotCopy,

pmMergePenNot, pmMaskPenNot,

prnMergeNotPen, pmMaskNotPen,

pmMerge, pmNotMerge, pmMask,

pmNotMask, pmXor, pmNotXor) ;

Содержит идентификатор одной из растровых операций, которые определяют взаимодействие пера с поверхностью. Эти операции соответствуют стандартным, определенным в Windows

property Style: TPenStyle ;

TPenStyle = (psSolid, psDash,

psDot, psDashDot, psDashDotDot,

psClear, psInsideFrame) ;

Определяет стиль линии, рисуемой пером. Соответствующие стили также определены в Windows

 

property Width: Integer;

Содержит значение толщины пера в пикселах

К сожалению, пунктирные и штрихпунктирные линии (стили psDash, psDot, psDashDot, psDashDotDot) могут быть установлены только для линий единичной толщины. Более толстые линии должны быть сплошными — такое ограничение существует в Windows.

Операция pmNotXor подходит для рисования перемещающихся линий или фигур, например, при выделении мышью какой-либо области. Если вы два раза нарисуете одну и ту же фигуру таким пером, то после первого раза она появится, после второго — полностью сотрется.

 Класс TBrush

Этот класс инкапсулирует свойства кисти — инструмента для заливки областей. Когда создается экземпляр этого класса, первоначально создается белая сплошная (styie-bssolid) кисть. Свойства класса приведены в табл. 8.3.

Таблица 8.3. Свойства класса TBrush

Свойство

Описание

Property Handle: HBrush;

Содержит дескриптор кисти

Property Color: TColor ;

Определяет цвет кисти

Property Style: TBrushStyle ;

TbrushStyle = (bsSolid,

bsClear, bsHorizontal,

bsVertical, bsFDiagonal,

bsBDiagonal, bsCross,

bsDiagCross);

Определяет стиль кисти (фактура закраски) 

Property Bitmap: TBitmap; 

Содержит битовую карту, определенную пользователем для закраски поверхностей. Если это свойство определено, то свойства Color и Style недействительны

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

Класс 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 и свойства, которые приведены ниже в табл. 8.4. При их рассмотрении имейте в виду, что все геометрические фигуры рисуются текущим пером. Те из них, которые можно закрашивать, закрашиваются с помощью текущей кисти. Кисть и перо при этом имеют текущий цвет.

Таблица 8.4. Методы и свойства класса rcanvas

Метод

Описание

procedure Arc (XI, Y1, Х2, Y2, Y3, Х4, Y4: integer);

 

 

 

Метод рисует сегмент эллипса. Эллипс определяется описывающим прямоугольником (Х1, Y1) — (Х2, Y2); его размеры должны лежать в диапазоне от 2 до 32767 точек. Начальная точка сегмента лежит на пересечении эллипса и луча, проведенного из его центра через точку (ХЗ, Y3). Конечная точка сегмента лежит на пересечении эллипса и луча, проведенного из его центра через точку (Х4, Y4). Сегмент рисуется против часовой стрелки

procedure Chord (XI, Y1, Х2, Y2, ХЗ, Y3, Х4, Y4 : Integer);

Рисует хорду и заливает отсекаемуюею часть эллипса. Эллипс, начальная и конечная точки хорды определяются как в методе Arc

procedure Ellipse (XI, Y1, Х2, Y2: Integer);

Рисует и закрашивает эллипс, вписанный в прямоугольник (Х1, Y1) - (Х2, Y2)

procedure LineTo(X, Y: Integer);

Проводит линию текущим пером из текущей точки в (X, Y)

procedure MoveTo(X, Y: Integer);

Перемещает текущее положение пера (свойство PenPos) в точку (X, Y)

procedure BrushCopy(constDest: TRect; Bitmap: TBitmap;

const Source: TRect; Color: TColor) ;

Производит специальное копирование. Прямоугольник Source из битовой карты Bitmap копируется в прямоугольник Dest на канве; при этом цвет Color заменяется на цвет текущей кисти(Brush.Color)

С помощью этого метода можно нарисовать "прозрачную" картинку. Для этого нужно выбрать соответствующий фону цвет кисти и затем заменить на него фоновый или наиболее часто встречающийся цвет битовой карты (см. Bitmap.TransparentColor)

procedure CopyRect(const Dest: TRect; Canvas: TCanvas;

const Source: TRect);

Производит копирование прямоугольника Source из канвы Canvas в прямоугольник Dest в области самого объекта

procedure FillRect(const Rect: TRect) ;

Производит заливку прямоугольника (текущей кистью)

procedure FrameRect(const Rect: TRect) ;

Производит оконтуривание Прямоугольника цветом текущей кисти (без заполнения).

procedure Draw(X, Y: Integer; Graphic: TGraphic);

Осуществляет рисование графического объекта Graphic (точнее, вызов метода его рисования) в области с верхним левым углом (X, Y)

procedure StretchDrawtconst Rect: TRect; Graphic: TGraphic) ;

Осуществляет рисование объекта Graphic в заданном прямоугольнике Rect. Если их размеры не совпадают, Graphic масштабируется

procedure DrawFocusRect(const Rect: TRect);

Производит отрисовку прямоугольной рамки из

точек (как на элементе, имеющем фокус ввода).

Поскольку метод использует логическую операцию XOR (исключающее ИЛИ), повторный вызов для того же прямоугольника приводит изображение к начальному виду

procedure FloodFill(X, Y: Integer; Color: TColor;

FillStyle: TFillStyle);

TFillStyle = (fsSurface, fsBorder) ;

Производит заливку области текущей кистью.

Процесс начинается с точки (X, Y). Если режим

FillStyle равен fsSurface, то он продолжа

ется до тех пор, пока есть соседние точки с цветом Color. В режиме fsBorder закрашивание,

наоборот, прекращается при выходе на границу

с цветом Color

procedure Pie(XI, Yl, X2, Y2, X3, Y3, X4, Y4: Integer); 

 

Рисует сектор эллипса, описываемого прямоугольником (Х1, Y1) — (X2, Y2). Стороны сектора лежат на лучах, проходящих из центра эллипса через точки (X3, Y3) и (X4, Y4)

procedure Polygon(const Points: array of TPoint);

Строит многоугольник, используя массив координат точек Points. При этом последняя точка соединяется с первой и внутренняя область закрашивается

procedure Polyline(const Points: array of TPoint);

Строит ломаную линию, используя массив координат точек Points

procedure PolyBezier(const Points: array of TPoint);

Строит кривую Безье (кубический сплайн), используя массив координат точек Points

procedure PolyBezierTo(const Points: array of TPoint); 

Строит кривую Безье (кубический сплайн), используя массив координат точек points. Текущая точка используется в качестве первой

procedure Rectangle(X1, Y1,X2, Y2: Integers);

Рисует прямоугольник с верхним левым углом в (Х1, Y1) и нижним правым в (X2, Y2)

Procedure RoundRect(XI, Yl,

X2, Y2, X3, Y3: Integers); 

Рисует прямоугольник с закругленными углами.

Координаты вершин — те же, что и в методе Rectangle. Закругления рисуются как сегменты эллипса с размерами осей по горизонтали и вертикали X3 и Y3

function TextHeight(const Text: string): Integer;

Задает высоту строки Text в пикселах 

function TextWidth(const Text: string): Integer;

Задает ширину строки Text в пикселах 

procedure TextOut(X, Y; Integer; const Text: string);

Производит вывод строки Text. Левый верхний угол помещается в точку канвы (X, Y)

procedure TextRect(Rect: TRect; X, Y: Integer; const Text: string) ;

 

Производит вывод текста с отсечением. Как и в

TextOut, строка Text выводится с позиции (X, Y); но при этом часть текста, лежащая вне пределов прямоугольника Rect, отсекается и не будет видна

Свойство

property ClipRect: TRect;

 

 

 

Определяет область отсечения канвы. То, что при рисовании попадает за пределы этого прямоугольника, не будет изображено. Свойство Доступно только для чтения — его значение переустанавливается системой в контексте устройства, с которым связана канва.

property PenPos: TPoint;

Содержит текущую позицию пера канвы (изменяется посредством метода MoveTo)

Метод

procedure Refresh;

сбрасывает текущие шрифт, перо и кисть, заменяя их на стандартные, заимствованные из установок Windows (black pen, hollow brush, system_font).

Предусмотрено два события для пользовательской реакции на изменение канвы:

property OnChange: TNotifyEvent;

property One-changing: TNotifyEvent;

Эти события возникают при изменениях свойств и вызовах методов TCanvas, меняющих вид канвы (то есть, при любом рисовании; в методе MoveTo, например, они не возникают). Отличие их в том, что событие onChanging вызывается до начала изменении, а событие OnChange — после их завершения.

Идентификатор (код) растровой операции при копировании прямоугольных блоков содержится в свойстве:

property CopyMode: TCopyMode;

TCopyMode = Longint;

и определяет правило сочетания пикселов, копируемых на канву, с ее текущим содержимым. При этом можно создавать разные изобразительные эффекты В Delphi определены следующие константы кодов: cmBlackness,

cmDstlnvert, cmMergeCopy, cmMergePaint, cmNotSrcCopy, cmNotSrcErase, cmPatCopy, cmPatInvort, cmPatPaint, cmSrcAnd, cmSrcCopy, cmSrcErase, crnSrcInvert, cmSrcPaint, cmWhiteness.

Все они стандартно определены в Windows, и подробное их описание можно найти в документации по GDI. Значением свойства CopyMode по умолчанию является cmSrcCopy — копирование пикселов источника поверх существующих.

Из возможностей, появившихся в классе TCanvas, следует отметить поддержку рисования кривых (полиномов) Безье. Эта возможность впервые появилась в API Windows NT. Для построения одной кривой нужны минимум четыре точки — начальная, конечная и две опорные. По ним будет построена кривая третьего порядка. Если задан массив точек, они используются для построения последовательных кривых, причем последняя точка одной кривой является первой для следующей кривой.

Хорошей иллюстрацией использования объекта TCanvas может послужить пример GraphEx, поставляемый вместе с Delphi (папка \Demos\Doc\GraphEx). Есть только одно "но" — он приводится в неизменном виде, начиная с версии Delphi 1.0. Поэтому сделаем часть работы за программистов Inprise. В нашем примере модернизированы панели инструментов — они выполнены на компонентах ттоо1ваг и TControlBar; добавлена поддержка файлов JPEG; и, наконец, добавлена возможность рисования кривых Безье. Обновленный внешний вид главной формы примера GraphEx показан на рис. 8.1.

Рис. 8.1. Так теперь выглядит главная форма примера GraphEx

Где же найти ту канву, на которой предстоит рисовать? Во-первых, ею снабжены все потомки классов TGraphicControl и TCustomControi, то есть почти все визуальные компоненты из Палитры компонентов; в том числе и форма. Во-вторых, канву имеет растровая картинка (класс TBitmap); вы можете писать и рисовать не на пустом месте, а на готовом изображении (об этом в разделе "Класс TBitmap" данной главы). Но иногда нужно рисовать и прямо на экране! В этом случае придется прибегнуть к использованию функций API. Функция Get dc возвращает контекст устройства заданного окна, если ей передается параметр О—то всего экрана:

ScreenCanvas := TCanvas.Create;

ScreenCanvas.Handle := GetDC(O);

// рисование на ScreenCanvas ReleaseDC(0, ScreenCanvas.Handle);

ScreenCanvas.Free;

Пример необходимости рисования на экране — экранные заставки (Screen savers). Такое приложение под названием MovLines поставляется с этой книгой.

Когда и где следует рисовать? Этот вопрос далеко не риторический, как может показаться с первого взгляда.

Помимо графических примитивов, таких как линии и фигуры, на канве можно разместить готовые изображения. Для их описания создан класс TGraphic.

Класс TGraphic

Канва, перо, кисть и шрифт нужны, чтобы нарисовать свою картинку. Чтобы загрузить готовую, нужны объекты, "понимающие" графические форматы Windows.

Абстрактный класс TGraphic является родительским для четырех видов изображений, общепринятых в графике Windows — значка (компонент Ticon), метафайла (компонент TMetafile) и растровой картинки (компонент TBitmap). Четвертым потомком TGraphic является TJpEGimage — сжатая растровая картинка в формате JPEG.

Работая над приложением в Delphi, вы никогда не будете создавать объекты класса TGraphic;, но переменной этого типа вы можете присваивать указатель на любой из перечисленных классов-потомков.

Метод:

procedure Assign(Source: TPersistent);

переопределяет одноименный метод предка, допуская полиморфное присваивание графических объектов.

Загрузку и выгрузку графики в поток осуществляют методы:

procedure LoadFromStream(Stream: TStream);

procedure SaveTo3tream(Stream: TStream);

а загрузку и выгрузку в файл — методы:

procedure LoadFromFile(const Filename: string);

procedure SaveToFile(const Filename: string);

Эти методы создают соответствующий файловый поток и затем вызывают

Методы LoadFromStream/SaveToStream.

Два метода осуществляют взаимодействие с буфером обмена Windows:

procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle; APal-ette: 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('rnyicon.ico'); //Создан и загружен объект класса TIcon

MyBitmap := Imagel.Picture.Bitmap; // прежний TIcon уничтожается

Если же вы описали свой класс (допустим, TGiFimage), то к его методам и свойствам следует обращаться так:

(Graphic as TGIFImage).MyProperty := MyValue;

Перечислим остальные методы и свойства:

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

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

Graphic.

APaiette: HPALETTE);

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

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

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

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

class procedure RegisterFileFormat(const AExtension, ADescription:

string; AGraphicClass: TGraphicClass);

class procedure RegisterClipboardFormat(AFormat: Word; AGraphicClass:

TGraphicClass);

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

property Width: Integers; property Height: Integer;

Ширина и высота картинки. Значения этого свойства всегда те же, что и у объекта из свойства Graphic.

Все три разновидности графических объектов имеют свои системы кэширования. Это означает, что на один реально существующий в системе (и занимающий долю ресурсов!) дескриптор могут одновременно ссылаться несколько объектов. Реализуется такое связывание через метод Assign. Выражение:

Iconl.Assign(Icon2);

означает, что два этих объекта разделяют теперь один фактически находящийся в памяти значок.

Более простым является кэширование для Ticon и metafile, которые умеют только отображать себя и не предназначены для редактирования (создатели Delphi считают, что дескриптор графического объекта дается программисту не для того, чтобы "ковыряться" в нем на уровне двоичных кодов). Гораздо сложнее устроен механизм кэширования для канала TBitmap, который имеет свою канву для рисования.

Внутреннее представление информации в графических объектах двоякое — Она может храниться как поток типа TMemoryStream (в него загружается содержимое соответствующего файла), как область памяти с дескриптором (структура которой зависит от типа графического объекта) и одновременно в двух этих видах, содержимое которых автоматически синхронизируется. Поэтому будьте готовы, что загрузка изображения потребует вдвое большего объема памяти — особенно это актуально для больших картинок.

Кого-то может удивить отсутствие объявленных методов рисования, вроде метода Draw для классов Ticon, TMetafile и TBitmap. Объяснение простое — в процессе рисования они играют пассивную роль; рисуют не они — рисуют их. Все рисование должно осуществляться через вызовы методов Draw и StretchDraw канвы, содержащей графику, ибо канва соответствует тому контексту, в котором должна осуществляться операция.

Рассмотрим предопределенные графические классы.

Класс TMetafile

Класс TMetafile инкапсулирует свойства метафайла Windows. С появлением Windows 95 к стандартному метафайлу (формат wmf) добавился расширенный (формат emf), обладающий расширенными возможностями. Соответственно в объекте TMetafile имеется свойство

property Enhanced: Boolean;

Внутреннее представление метафайла всегда новое (emf), и устанавливать свойство Enhanced в False следует только для обеспечения совместимости со старыми программами.

В классе TMetafile перекрываются методы Assign, LoadFromStream, SaveToStream, LoadFromClipboardFomat, SaveToClipboardFormat. В буфер обмена объект помещает свое содержимое в формате CF_ENHMETAFILE. Помимо общих, класс имеет следующие свойства:

property Description: string;

property CreatedBy: string;

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

Класс TIcon

Этот класс инкапсулирует значок Windows.

Не пытайтесь изменить размеры значка — они по определению постоянны (и равны GetSystemMetrics(SM_CXICON) И GetSystemMetrics(SM_CYICON)), и при попытке присвоить новые значения возникает исключительная ситуация EinvaiidGraphicOperation. Значок нельзя также читать и писать в буфер обмена, так как в Windows нет соответствующего формата.

Свойство Transparent для значка всегда равно True. Изменить его нельзя — значки прозрачны также по определению.

В этом классе перекрываются методы класса TGraphic: Assign, LoadFromStream и SaveToStream. Дополнительно также определены:

Класс TBitmap

Класс TBitmap является основой растровой графики в Delphi. В первых версиях среды этот класс соответствовал битовой карте, зависимой от устройства (Device Dependent Bitmap, DDB). Этот формат хорош для деловой графики — отображения небольших картинок с малой глубиной цвета, например, на кнопках. Формат DDB родился во времена первых версий Windows, когда еще не было графических ускорителей и кое-где еще помнили о EGA. Поэтому и форматы хранения были привязаны к определенным видеорежимам.

Со временем аппаратура совершенствовалась, росло и количество поддерживаемых видеорежимов. Появились режимы High Color (15—16 бит на точку) и True Color (24 бита на точку). Все это привело к тому, что картинка стала храниться в аппаратно-независимом формате (Device Independent Bitmap, DIB), а проблемы ее быстрого отображения легли на аппаратуру и драйверы.

За формат битовой карты — DIB или DDB — отвечает свойство:

type TBitmapHandleType = (bmDIB, bmDDB);

property HandleType: TBitmapHandleType;

По умолчанию устанавливается режим bmDIB. Впрочем, можно заставить приложение, написанное на Delphi, вернуться к старому типу. Для этого нужно установить значение глобальной переменной DDBsOnly (модуль graphics. pas) равной True. Впрочем, необходимость этого сомнительна. Все новые видеокарты и драйверы к ним, а также графические интерфейсы (такие, как DirectX), оптимизированы для использования DIB.

Желаемую глубину цвета битовой карты можно узнать и переустановить, меняя значение свойства:

TPixelFormat = (pfDevice, pflbit, pf4bit, pfSbit, pfl5bit, pfl6bit, pf24bit, pf32bit, pfCustom);

property PixelFormat: TPixeiFormat;

Формат pf Device соответствует битовой карте DDB. Глубина цвета в 1, 4 и 8 бит на пиксел — традиционная и предусматривает наличие у изображения палитры. Другие режимы предусматривают хранение непосредственных яркостей точек в каждом из трех основных цветов — красном (R), зеленом (G) и синем (В). Разрядность 15 бит соответствует распределению бит 5-5-5 (RGB 555), 16 бит - RGB 565, 24 бит -RGB 888. Режим 32 бит похож на 24-битный, но в нем дополнительно добавлен четвертый канал (альфа-канал), содержащий дополнительную информацию о прозрачности каждой точки. Формат pf custom предназначен для реализации программистом собственных графических конструкций. В стандартном классе TBitmap установка свойства PixelFormat в pfCustom приведет к ошибке — поэтому использовать его нужно только в написанных вами потомках TBitmap.

Битовая карта является одним из видов ресурсов. Естественно, что класс TBitmap поддерживает загрузку из ресурсов приложения:

procedure LoadFromResourceID(Instance: THandle; ResID: Integers-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;

возвращают дескрипторы битовой карты и палитры соответственно и после этого обнуляют дескрипторы, то есть как бы "отдают" их пользователю.

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

Битовая карта может быть монохромной и цветной, что определено свойством:

property Monochrome: Boolean;

Значение True соответствует монохромной битовой карте. При его изменении происходит преобразование содержимого к требуемому виду.

За прозрачность битовой карты отвечают свойства:

property TransparentColor: TColor;

type TTransparentMode - (tmAuto, tmFixed);

property TransparentMode: TTransparentMode;

Если значение свойства TransparentMode установлено равным tmAuto, за прозрачный (фоновый) принимается цвет верхнего левого пиксела. В противном случае этот цвет берется из свойства TransparentColor.

Битовая карта может использоваться в качестве маски для других битовых карт. В этом случае она превращается в двухцветную, где в белый цвет превращаются точки фона (см. TransparentColor), а в черный — все остальные. Для поддержки этого режима служат следующие методы и свойства:

procedure Mask(TransparentColor: TColor);

property MaskHandle: HBitmap;

function ReleaseMaskHandle: HBitmap;

Наконец, последним по счету будет рассмотрено очень важное свойство битовой карты TBitmap. Если формат ее хранения — DIB, то есть возможность получить доступ к данным самой битовой карты:

property ScanLine[Row: Integer]: Pointer;

Это свойство представляет собой массив указателей на строки с данными битовой карты. Параметр Row содержит номер строки. Следует помнить, что в большинстве случаев строки в битовой карте упорядочены в памяти снизу вверх и фактически первой после заголовка хранится нижняя строка. Код, возвращающий значение свойства scanLine, это учитывает; поэтому не удивляйтесь, если с ростом параметра Row значение свойства уменьшается.

Внутри строки данные упорядочены в соответствии с форматом (PixeiFormat). Для формата pfSbit все просто — каждый байт в строке соответствует одному пикселу. Для pflSbit и pfl6bit пикселу соответствуют два байта (в этих 16 битах упакованы данные о трех каналах), pf24bit - три байта (по байту на канал).

Примерно так может выглядеть обработчик события onMouseMove, выводящий на панель состояния информацию о яркости в данной точке (подразумевается, что формат битовой карты — 8 или 24 бита):

procedure TMainForm.ImagelMouseMove(Sender: TObject; Shift: TShiftState;

X, Y: Integers);

begin

if not Assigned(Imagel.Picture.Bitmap) then Exit;

with Imagel.Picture.Bitmap, do case PixelFormat of

pfSbit: Statusbarl.SimpleText := Fonnat('x: %d y: %d b: %d',[x,y, pByteArray(ScanLine[y])^[x] ]) ;

pf24bit: Statusbarl. SimpleText :=Format('x: id y: %d R: %d,G: %d, B: %d',

[x,y, pByteArray(ScanLine[y])^[3*x], pByteArray(ScanLine[y])"[ 3*x+l], pByteArray(ScanLine[у])л[ 3*x+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 CardsDIl : THandle;

BackBitmap : Graphics.TBitmap;

initialization

CardsDIl := LoadLibraryEx('cards.dll',0, LOAD_LIBRARY_AS_DATAFILE);

BackBitmap := Graphics.TBitmap.Create;

BackBitmap. LoadFromResourceID(CardsDIl, 64);

finalization

BackBitmap.Free;

FreeLibrary(CardsDIl);

end.

В Windows 95/98 эта динамическая библиотека— 16-разрядная, и работать так, как описано, не будет. Используйте cards .dll из состава Windows NT.

Аналогичным образом можно загрузить битовые карты для всей колоды. При показе карты, в зависимости от того, открыта она или закрыта, отрисовывается один из объектов 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 CompressionQuality: TJPEGQualityRange;

Оно определяет качество сжимаемого изображения и его размер. При малых значениях этого свойства файлы получаются очень маленькими, но с большими искажениями (напомним, что стандарт JPEG предусматривает сжатие с потерями качества). При значениях, близких к 100, потери незаметны, но и размер файла при этом максимален.

По умолчанию значение этого свойства равно 75, что обеспечивает разумный компромисс между размером и качеством.

Заранее предсказать размер сжатого файла нельзя — разные картинки сжимаются по-разному, даже при одном значении CompressionQuality.

Кроме CompressionQuality, на качество отображения может повлиять и свойство

type TJPEGPerformance = (jpBestQuality, jpBestSpeed);

property Performance: TJPEGPerformance;

Оно нужно только при распаковке и отвечает за способ восстановления цветовой палитры из сжатой информации. На качество записываемого изображения оно никак не влияет.

Как и у TBitmap, у TJPEGImage есть свойство

type TJPEGPixelFormat = (jf24Bit, jf8Bit);

property PixelFormat: TJPEGPixelForm;

Для рассматриваемого объекта возможных значений всего два— jfSbit и jf24bit. По умолчанию используется 24-битный формат. Если информация о цвете не нужна, то можно установить значение свойства Grayscale равным True — в этом случае изображение будет записано (или распаковано) в полутоновом виде (256 оттенков серого).

Свойства ProgressiveEncoding И ProgressiveDisplay определяют способ показа изображения при распаковке. Первое из них отвечает за порядок записи в файл сжатых компонентов. Если значение свойства ProgressiveEncoding установлено равным True, начинает играть роль свойство ProgressiveDisplay. От его значения зависит, будет ли показываться изображение по мере распаковки (при значении True), либо будет сначала полностью распаковано, а потом показано (при значении False).

Чтобы организовать предварительный просмотр большого числа больших изображений, уместно воспользоваться свойством:

type TJPEGScale = (jsFullSize, jsHalf, jsQuarter, jsEighth);

property Scale: TJPEGScale;

Искушенные в графике специалисты зададут вопрос: зачем оно? Ведь можно прочитать изображение, а затем уменьшить его масштаб стандартными способами. Представление информации в файлах JPEG таково, что можно достаточно просто извлечь изображение сразу в нужном масштабе. Таким образом достигается двойной выигрыш — на времени распаковки и на времени отображения.

Печать растровых изображений может вызвать проблемы при согласовании его размеров с размерами листа принтера и его разрешением. Большую часть из них можно снять, изучив пример, поставляемый с Delphi — jpegproj. Он находится не в папке \Denios, как обычно, а в папке Help\Examples\Jpeg.

В заключение отметим, что класс TJpEGimage не имеет своей канвы для рисования — для этого его нужно преобразовать в TBitmap.

Компонент TImage

Этот компонент служит надстройкой над классом TPicture и замыкает всю иерархию графических объектов VCL. Именно на его поверхности и будут отображаться графические объекты, содержащиеся в свойстве:

property Picture: TPicture;

В качестве канвы компонента (свойство canvas) используется канва объекта из свойства picture. Graphic, но только если поле Graphic ссылается на объект класса TBitmap. Если это не так, то попытка обращения к свойству вызовет исключительную ситуацию Einvalidoperation, так как рисовать на метафайле или значке нельзя.

Следующие три свойства определяют, как именно графический объект располагается в клиентской области компонента:

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

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

property Center: Boolean;

Если это свойство равно True, изображение центрируется в пределах клиентской области. В противном случае оно располагается в ее верхнем левом углу.

Несмотря на то что описанию свойств и методов графических объектов здесь отведено уже довольно много места, работа с ними проста и удобна. Программу для просмотра изображений в среде Delphi можно создать буквально "в три счета":

  1. Поместите на форму следующие компоненты: область прокрутки TScroiiBox, на нее — компонент Timage (их верхние левые углы должны совпадать), любую кнопку (например, TButton) и диалог открытия файлов TOpenPictureDialog.
  2. Подключите к главному модулю создаваемого приложения модуль JPEG (в предложении uses); значение свойства Autosize компонента Timage установите равным True.
  3. Дважды щелкните мышью на кнопке. В появившемся обработчике события onciick напишите такой код:

procedure TForml.BitBtnIClick(Sender: TObject);

begin

OpenPictureDialogI.Filter := GraphicFilter(TGraphic);

if OpenPictureDialogI.Execute

then Imagel.Picture.LoadFromFile(

OpenPictureDialogI.FileName);

end;

Приложение готово. Обратите внимание на роль полиморфизма в методе LoadFromFile — по расширению файла определяется его формат и в зависимости от этого создается нужный графический объект.

Использование диалогов для загрузки и сохранения графических файлов

Для удобства открытия картинок существует пара компонентов-диалогов:

TOpenPictureDialog И TSavePictureDialog.

Список форматов открываемых файлов определяется свойством Filter. Можно, как в случае со стандартными диалогами TOpenDialog или TSaveDialog, сформировать их вручную с помощью редактора свойства Filter. Можно поступить проще, воспользовавшись готовыми средствами. Для удобства формирования строк графических фильтров существуют три специальные функции:

Формирует строку с полным текстом графического фильтра, позволяющего открывать все файлы, форматы которых являются потомками параметра GraphicClass. Если в качестве параметра этой функции будет передан класс TGraphic, то в строке будут перечислены все форматы:

'All

(*.jpg;*.jpeg;*.bmp;*.ico;*.emf;*.wmf)|*.jpg;

*.jpeg;*.bmp;*.ico;*.emf;

*.wmf|JPEG Image File (*.jpg)|*.jpg|JPEG Image File (*.jpeg)|*.jpeg|Bitmaps (*.bmp)|*.brnplIcons (*.ico)I*.ico Enhanced Metafiles (*.emf)I*.emf|Metafiles (*.wmf)|*.wmf

Формат JPEG появляется в перечне, если в приложении используется модуль с тем же названием — JPEG. В приводимом ниже примере возникла необходимость совместить фильтры только для классов TBitmap и

TJPEGlmage, которые не являются предками друг друга. В этом случае получившиеся строки нужно соединить, используя символ конкатенации (|):

S := GraphicFilter(TBitmap)+'|'+GraphicFilter(TJpegImage) D function GraphicExtension(GraphicClass: TGraphicClass): string;

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

Эта функция возвращает перечень расширений файлов с форматами — потомками GraphicClass, перечисленных через точку с запятой.

Для диалогов предусмотрено несколько событий, которые программист может обработать самостоятельно. Первые три — достаточно тривиальные: onshow, OnCanciose и enclose. Нужно предостеречь программиста: по чьему-то недосмотру последние два события вызываются только в случае нормального завершения диалога (нажатием кнопки Open или Save), а если завершить диалог нажатием кнопки Cancel или крестика на заголовке диалога, управления они не получат.

Другие три события связаны с изменениями, которые осуществляет пользователь во время выбора нужного файла. Они происходят в момент изменения формата открываемого файла (событие onTypeChange), изменения текущей папки (OnFolderChange) И текущего файла (OnSelectionChange).

Но разработчики диалогов не предусмотрели одну очень нужную возможность. Дело в том, что у разных графических форматов возможны различные параметры и варианты. Если вы имеете опыт работы с графическими пакетами вроде Adobe Photoshop или Corel, то знаете, что в зависимости от выбранного формата сохранения данных диалоговое окно изменяет свой внешний вид — к нему добавляются элементы управления, соответствующие параметрам формата.

Поступим так и мы, предусмотрев настройку при сохранении файлов формата JPEG. Для этого будет использовано событие OnTypeChange компонента TSavepietureDiaiog. Для события нужно проверить значение свойства Filterindex. Если оно больше 1 (то есть выбраны файлы формата JPEG), нужно увеличить высоту окна диалога и разместить на нем дополнительные компоненты: флажок, соответствующий свойству progressiveEncoding, и редактор свойства CompressionQuality (рис. 8.2). Если тип файла снова поменялся и стал равным 1, нужно эти компоненты убрать.

Поможет нам в этом внимательное изучение исходных кодов диалогов, находящихся в модуле extdlgs.pas. Программисты Inprise пошли по пути модернизации внешнего вида стандартных диалоговых окон, добавив к ним справа панель для отображения внешнего вида открываемых (записываемых) картинок. Можно пойти дальше и добавить таким же образом и свои элементы управления.

Приводимый ниже пример ModifDIg — усовершенствованная программа просмотра и сохранения файлов растровой графики, к которым относятся файлы форматов JPEG и BMP. Чтобы исключить метафайлы и значки (*.wmf, *.emf, *.ico), соответствующим образом настраиваются фильтры в диалогах открытия и сохранения.

Рис. 8.2. Внешний вид модифицированного компонента TSavePict

Для изменения размеров диалогового окна нужно отыскать в его состав компонентов панель picturepanei (так назвали Inprise) и увеличить ее высоту. Следует также поменять и ра ских окон. Поскольку они не являются компонентами Del] диалоговые окна являются составными частями Windows). Используются функции API GetWindowRect и SetWindowPos.

Обратите также внимание, что при загрузке используется событие Onprogress класса TGraphic. В его обработчике информации деланной работы отображается на компоненте progressBar1 картинок обработчик вызывается только в начале и конце зователь ничего не заметит. Зато при загрузке большого будет спокоен, видя, что процесс загрузки идет и машина не

 

Листинг 8.1 Исходный текст главного модуля программы ModifDig

unit mainUnit;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

ExtDIgs, StdCtrls, ComCtrls, ExtCtrls, Buttons;

type

TFonnl = class(TForm)

SavePictureDialogI: TSavePictureDialog;

OpenPictureDialogI: TOpenPictureDialog;

ScrollBoxl: TScrollBox;

Image 1: TImage;

ProgressBarl: 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

Formi: TFonnl;

implementation

<$R *.DFM) uses jpeg;

const DeltaH : Integer = 80;

var Quality : TJpegQualityRange;

ProgressiveEnc : Boolean;

procedure TForml.OpenBitBtnClick(Sender: TObject);

begin if OpenPictureDialogI.Execute

then Imagel.Picture.LoadFromFile

(OpenPictureDialogI.FileName);

end;

procedure TForml.SaveBitBtnClick(Sender: TObject);

var ji : TJpegImage;

begin if SavePictureDialogl.Execute then

begin

ji := TJpegImage.Create;

ji.CompressionQuality := Quality;

ji.ProgressiveEncoding := ProgressiveEnc;

ji.Assign(Imagel.Picture.Bitmap);

ji.SaveToFile(SavePictureDialogl.FileName);

j i.Free ;

end;

end;

procedure TFormI.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 := aINone;

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+DeitaH,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:='JChe;

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.Imagel

Progress(Sender: TObject; Stage:

TprogressStage;

PercentDone: Byte; RedrawNow:

Boolean; const R: TRect;

const Msg: Strings-begin case Stage of psStarting: begin

Progressbarl.Position :- 0;

Progressbarl.Max := 10.0;

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')<>nil then begin FindComponent('JLabel').Free;

FindComponent('JEdit').Free;

ProgressiveEnc := (FindComponent('JCheck') as TCheckBox).Checked;

FindComponent('JCheck').Freer; 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. FomCreate (Sender: TObject);

var 3: string;

begin

s :=GraphicFilter(TBitmap)+'| 4-GraphicFilter(TJpegImage);

OpenPictureDialogl.Filter := s;

SavePictureDialogI.Filter := s;

end;

procedure TForml.SavePictureDialoglShow(Sender: TObject);

begin

with Sender as TSavePictureDialog do

begin

if FindComponent('JLabel')Onil then

begin

Filterlndex := 2;

SavePictureDialoglTypeChange(Sender);

end;

end;

end;

end.

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

Класс TCIipboard

Класс TCIipboard предоставляет программисту интерфейс с буфером (папкой) обмена (Clipboard) Windows. При включении в проект модуля clipbrd.pas глобальный объект clipboard создается автоматически и доступен приложению в течение всего времени его работы.

Методы открытия и закрытия буфера обмена:

procedure Open;

procedure Close;

вызываются во всех остальных методах TCIipboard, поэтому программист редко нуждается в обращении к ним. В объекте ведется счетчик числа обращений к этим функциям, так что соответствующие функции API Windows вызываются только при первом открытии и последнем закрытии.

Очистка содержимого буфера (для всех форматов) производится вызовом метода:

procedure Clear;

О доступных форматах можно узнать, пользуясь следующими свойствами и методами:

Содержит число форматов в буфере на данный момент.

Содержит их полный список.

Волею разработчиков различаются способы обмена графической и текстовой информацией через буфер обмена. Рассмотрим их независимо.

Через вызов метода

procedure Assign(Source: TPersistent);

в буфер обмена помещаются данные классов TGraphic, точнее, его потомков — классов TBitmap (формат CF_BITMAP) и TMetafile (CF_ENHMETAFILE), a также данные класса Tpicture. Данные класса Ticon не имеют своего формата и с классом TClipboard несовместимы.

Допустимо и обратное: в TClipboard есть специальные (скрытые) методы для присваивания содержимого объектам классов Tpicture, TBitmap и TMetafile. Допустимы выражения вида:

My Image.Picture.Assign(Clipboard);

Clipboard.Assign(My Image.Picture);

Для работы с текстом предназначены методы:

Читает текст из буфера обмена в буфер Buffer, длина которого ограничена значением BufSize. Функция возвращает истинную длину прочитанного текста.

Помещает текст из Buffer в буфер обмена в формате CF_TEXT.

Впрочем, можно поступить проще.

Свойство

property AsText: string;

соответствует содержимому буфера обмена в текстовом формате CF_TEXT (приведенному к типу string). При отсутствии в буфере данных этого формата возвращается пустая строка.

Методы:

function GetAsHandle(Format: Word): THandle;

procedure SetAsHandle(Format: Word; Value: THandle);

соответственно читают и пишут данные в буфер в заданном формате Format. При чтении возвращается дескриптор находящихся в буфере данных (или 0 при отсутствии данных). Для дальнейшего использования эти данные должны быть скопированы. При записи данные, передаваемые в параметре value, в дальнейшем должны быть уничтожены системой (а не программой пользователя).

Два метода предназначены для обмена компонентами через буфер обмена (в специально зарегистрированном формате CF_COMPONENT):

function GetComponent(Owner, Parent: TComponent): TComponent;

procedure SetComponent(Component: TComponent);

Они используются составными частями среды Delphi.

Класс TScreen

Этот компонент представляет свойства дисплея (в Windows 98 и 2000 — нескольких дисплеев), на котором выполняется приложение. Поскольку экземпляр данного класса только один (он создается системой при запуске приложения), то большинство методов и свойств имеют информационный характер и недоступны для записи.

Курсор приложения, общий для всех форм, доступен через свойство:

property Cursor: TCursor;

Часто приходится включать "песочные часы" на время выполнения длительной операции. Правильнее всего это сделать следующим образом:

Screen.Cursor := crHourglass;

try

{Calculations... } finally

Screen.Cursor := crDefauit;

end;

Имеется список всех курсоров. Получить дескриптор курсора с индексом index можно при помощи свойства:

property Cursors[Index: Integer]: HCURSOR;

Напомним, что индексы зарегистрированных курсоров лежат в диапазоне от -22 (crSizeAll) до 0 (crDefauit).

Рассмотренный ниже фрагмент кода при инициализации формы заносит имена всех зарегистрированных в системе курсоров в список ListBoxl. Затем, при выборе элемента списка, устанавливается соответствующий ему курсор:

procedure TForml.FormCreate(Sender: TObject);

type

TGetStrFunc = function(const Value: string): Integer of object;

var

CursorNames: TStringList;

AddValue: TGetStrFunc;

begin

CursorNames := TStringList.Create;

AddValue := CursorNames.Add;

GetCursorValues(TGetStrProc(AddValue));

ListBoxl.Items.Assign(CursorNames);

end;

procedure TForml.ListBoxlClick(Sender: TObject);

begin

Screen.Cursor := StringToCursor(ListBoxl.Items[ListBoxl.Itemlndex]);

end;

Список курсоров, функции GetCursorValues, StringToCursor и некоторые другие содержатся в модуле controls. pas.

Имена всех установленных в системе шрифтов помещаются в список, определенный в свойстве:

property Fonts: TStrings;

Компонент сообщает неизменяемые свойства экрана (в данном видеорежиме). Его размеры в пикселах определены в свойствах:

property Height: Integer;

property Width: Integer;

В последних версиях ОС Microsoft имеется поддержка отображения на нескольких мониторах одновременно. Для этой цели предусмотрены свойства:

property MonitorCount: Integer;

property Monitors[Index: Integer]: TMonitor;

Каждый компонент TMonitor несет информацию о размерах и положении изображения на его экране. Образовавшийся же виртуальный рабочий стол характеризуется свойствами:

property DesktopLeft: Integer; property DesktopTop: Integer;

property DesktopWidth: Integer;

property DesktopHeight: Integer;

Все координаты отсчитываются от верхнего левого угла экрана первого монитора. Если монитор один, значения этих свойств совпадают с Left, Top, Width и Height.

С исходными текстами Delphi 5 поставляется модуль multimun. pas, содержащий прототипы структур и функций Windows 98/2000 для работы с многими мониторами.

Число точек на дюйм экрана содержится в свойстве:

property PixelsPerInch: Integer;

При появлении каждая форма заносит себя в список форм глобального объекта Screen. Два (доступных только для чтения) свойства дают информацию об этом списке:

property Forms[Index: Integer]: TForm;

property FormCount: Integer;

Нужно иметь в виду, что в списке указаны только формы, открытые приложением, а не все окна системы.

Следующие два свойства указывают на активную в данный момент форму и ее активный элемент управления:

property ActiveControl: TWinControl;

property ActiveForm: TForm;

При их изменении генерируются, соответственно, события:

property OnActiveControlChange: TNotifyEvent;

property OnActiveFormChange: TNotifyEvent;

Хоть и "некстати", расскажем здесь о свойстве

property DefaultKbLayout: HKL;

Оно указывает на раскладку клавиатуры, принятую в системе по умолчанию. Часто раскладку клавиатуры нужно переключать программно, чтобы облегчить жизнь пользователю. Так, в приложении, в котором нужно быстро вводить в базу данных большой объем информации на русском и английском языках, такое переключение при смене полей просто необходимо.

Сначала нужно прочитать список имеющихся в системе раскладок и установить нужную:

var RusLayout, EngLayout : THandle;

procedure TMainForm. FomCreate (Sender: TObject);

var Layouts : array[0..7] of THandle;

i,n : Integer;

begin

// считывание раскладок

RusLayout := 0; EngLayout := 0;

n := GetKeyboardLayoutList(High(Layouts)+1, Layouts);

if n>0 then

for i:=0 to n-1 do

if LoWord(Layouts[i]) and $FF = LAMG_RUSSIAN then RusLayout := Layouts[i]

else if LoWord(Layouts[i]) and $FF = LANG_ENGLISH then EngLayout := Layouts[i];

// если есть, включим русскую

if RusLayoutOO then ActivateKeyboardLayout(RusLayout,0);

end;

Затем при входе в определенное поле (компонент редактирования данных) и выходе из него можно программно сменить раскладку:

procedure TMainForm.EditDocSerEnterfSender: TObject);

begin

if EngLayoutoO then ActivateKeyboardLayout(EngLayout,0);

end;

procedure TMainForm.EditDocSerExit(Sender: TObject);

begin

if RusLayoutoO then ActivateKeyboardLayout(RusLayout,0);

end;

Вывод графики с использованием отображаемых файлов

Спору нет, объект TBitmap удобен и универсален. Программисты Inprise шагают в ногу с разработчиками графического API Windows, и исходный код модуля graphics. pas от версии к версии совершенствуется. Но в ряде случаев возможностей, предоставляемых стандартным компонентом, недостаточно. Один из таких случаев — работа с большими и очень большими изображениями (до сотен мегабайт). С ними приходится иметь дело в полиграфии, медицине, обработке изображений дистанционного зондирования Земли из космоса и т. п. Здесь TBitmap не подходит, так как запрашивает для хранения и преобразования картинки слишком много ресурсов.

Что делать? На помощь следует призвать Windows API, поддерживающий файлы, отображаемые в память (Memory Mapped Files). У них много полезных свойств, но здесь важно только одно из них. При создании битовой карты Windows распределяет для нее часть виртуального адресного пространства. А оно не безгранично — для выделения пятидесяти — ста мегабайт может не хватить размеров файла подкачки, не говоря уже об ОЗУ. Но можно напрямую отобразить файл в виртуальную память, сделав его частью виртуального адресного пространства. В этом случае нашему файлу с изображением будет просто выделен диапазон адресов, которые можно использовать для последующей работы.

Процедура отображения файла в память и присвоения адреса его данным выглядит следующим образом:

Var Memory: pByteArray;

ес : Integer-procedure TFormI.OpenlClick(Sender: TObject);

var

i: integer;

bmFile : pBitmapFileHeader;

bmlnfo : pBitmapInfoHeader;

begin if not OpenDialogI.execute then Exit;

hf := CreateFile(pChar(OpenDialogI.FileName), GENERIC_READ or GENERIC WRITE,

FILE_SHARE_READ, nil, OPENJ5XISTING, 0, 0);

if hf=INVALID_HANDLE_VALUE then begin

ec:=GetLastError;

ShowMessage(' File opening error '+IntTostr(ec));

Exit;

end;

hm := CreateFileMappingfhf, nil, PAGE_READONLY, 0,0,nil);

if пт=0 then begin ShowMessage(' File Mapping error %d',[GetLastError]);

Exit;

end;

pb := MapViewOfFile(hm, FTLK_MAP_READ, 0,0,0);

if pb=nil then begin ec:=GetLastError;

ShowMessage('Mapping error '+IntTostr(ec));

Exit;

end;

bmFile := pBitmapFileHeader(pb);

if (bmFile".bfTypeo<>$4D42) then BEGIN Exit;

END;

Memory:=@(pb^[bmFile^.bfOffBits]);

bmlnfo := @(pb^[SizeOf(TBitmapFileHeader)]);

StrLen:=(((bmlnfo^.biWidth*bm!nfo^

.biBitCount)+31) div 32)*4;

PaintMe(Self);

end;

В этом коде последовательно получены дескрипторы файла (hf, с помощью функции createFile), его отображения в память (hm, с помощью функции createFiieMapping) и указатель на отображенные данные (pb, посредством MapViewOfFile). He будем вдаваться в детали внутренней реализации битовой карты — графический формат BMP известен достаточно хорошо. Отметим только, что результатом проделанных операций являются структура bminfo типа TBitmapinfo, полностью характеризующая битовую карту, и указатель Memory на данные битовой карты. Теперь загруженные данные нужно суметь нарисовать на канве, в данном случае на канве объекта paintBox. Делается это следующим образом:

procedure TFormI.PaintMe(Sender: TObj ect);

var OldP : hPalette;i : integer;

begin

if Memory=nil then Exit;

OldP := SelectPalette(PaintBox.Canvas.Handle, Palette, False);

RealizePalette(PaintBox.Canvas.Handle);

SetStretchBltMode(PaintBox.Canvas.Handle, STRETCH_DELETESCANS);

case ViewMode of vmStretch:

with bminfo^ do

i := Stretch DIBits(PaintBox.Canvas.Handle,0,0,

PaintBox.Height,PaintBox.Width,

0,0,biWidth,Abs(biHeight),

Memory, pBitmapInfo(hminfo)^,

DIB_RGB_COLORS,

PaintBox.Canvas.CopyMode);

vmlxl:

with bminfo^,PaintBox.ClientRect do

i:=SetDIBitsToDevice(PaintBox.Canvas.

Handle,Left,Top,Right-Left,Bottom-Top,

Left,Top,Top,Bottom-top, Memory, pBitmapInfo(bminfo)", DIB_RGB_COLORS) ;

vmZoom:

begin

with bminfo'"', PaintBox. ClientRect do

i := StretchDIBits(PaintBox.Canvas.Handle,Left,Top,Right-Left,Bottom-Top,

0,0,biWidth,Abs(biHeight), Memory, pBitmapInfo (bminfo)^, DIB_RGB_COLORS,

PaintBox.Canvas.CopyMode);

end;

end;

if (i=0) or (i=GDI_ERROR) then

begin

ec :=GetLastError;

Formi.Caption := 'Error code '+IntToStr(ec);

end;

SelectPalette(PaintBox.Canvas.Handle, OldP, False);

end;

В зависимости от установленного режима отображения (vmstretch, vmzoom или vmlxl) применяются разные функции Windows API: StretchDiBits или SetDiBitsToDevice. Выигрыш в скорости работы приложения особенно ощущается, если загружаемые файлы становятся велики и должны размещаться в файле подкачки. Наше же приложение не использует его и отображает данные прямо из файла на экран (рис. 8.3).

Рис. 8.3. Этот снимок с метеорологического спутника имеет размер десятки мегабайт

 Класс TAnimate

В заключение — несколько слов для тех, кто хочет применить в своих программах анимированные (движущиеся) картинки. Самый простои путь для этого — быстрая смена нескольких последовательных битовых карт. Но, во-первых, их еще нужно нарисовать; во-вторых, если у вас достаточно большие картинки или недостаточно мощный компьютер, обязательно будет заметно мерцание, задержки и другие проблемы с выводом на экран. С появлением очередной версии библиотеки элементов управления COMCTL32 гораздо проще применить готовый компонент TAnimate.

Этот компонент предназначен для воспроизведения на форме файлов формата AVI (audio-video interleaved; появился впервые с выходом пакета Microsoft Video for Windows).

Альтернативными источниками таких файлов могут послужить:

property FileName: TFileName;

property ResHandle: THandie;

property ResID: Integer-property

ResName: string;

Наконец, если вы не запаслись своим AVI-файлом, то можете воспользоваться готовым, имеющимся в Windows и иллюстрирующим один из происходящих в системе процессов. Для этого из списка свойства CommonAvi нужно выбрать один из вариантов (рис. 8.4).

Рис. 8.4. Так выглядит ролик "перенос файлов"

Все эти свойства при своей установке обнуляют прочие альтернативные варианты. Запуск ролика начинается при установке значения свойства Active равным True; при этом показываются кадры, начиная со StartFrame и до stopFrame. Число повторений этой последовательности кадров задается свойством Repetitions; если вам нужен бесконечный цикл, установите это свой ство равным 0.

Что особенно удобно, компонент TAnimate снимает проблемы синхронизации показа ролика с другими процессами в системе и вашем приложении. Если свойство Timers равно False, показ ролика происходит в отдельном программном потоке и никак не влияет на остальное; если оно равно True,

ролик синхронизируется по внутреннему таймеру. Вы можете привязать к показу ролика, например, проигрывание звука.

Резюме

Графика — не самый маленький и не самый простой раздел программирования в Windows. Описанные в этой главе объекты Delphi сглаживают многие острые углы, но все равно начинающему без синяков и шишек не обойтись. Если у вас есть время и серьезные намерения, посидите над исходным текстом модуля graphics. pas — лучшего пособия для самообразования не найти.

С различными применениями графики вы столкнетесь на протяжении всей книги. Компонент TimageList будет рассмотрен в главе 11. Отображаемые файлы вы можете изучить по главе 6.