Delphi для профессионалов

         

ГЛАВА 10


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

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

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

 

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


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



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

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

property OnChange: TNotifyEvent;

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

 

Класс TFont


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

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

Таблица 10.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: TFontCharset TFontCharset = 0..255;

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

property Pitch: TFontPitch;

TFontPitch = (fpDefault, fpVariable, fpFixed);

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

property Height: Integer;

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

property PixelsPerlnch: Integer;

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

property Size: Integer;

Содержит размер шрифта в пунктах (как принято в Windows). Это свойство связано с Height соотношением:  Font. Size := -Font . Height*72/ Font . PixelsPerlnch

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

Свойство

Описание

property Handle: HPen;

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

property Color: TColor;

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

property Mode: TPenMode; TPenMode = (pmBlack, pmWhite, pmNop, pmNot, pmCopy, pmNotCopy, pmMergePenNot, pmMaskPenNot, pirMergeNotPen, pmMaskNotPen, pmMerge, pmNotMerge, pmMask, pmNotMask, pmXor, piriNotXor) ;

Содержит идентификатор одной из растровых операций, которые определяют взаимодействие пера с поверхностью. Эти операции соответствуют стандартным, определенным в 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) кисть. Свойства класса приведены в табл. 10.3.

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

Таблица 10.4.  Методы класса TCanvas

Метод

Описание

procedure Arc (XI, Yl, Х2, Y2, ХЗ, Y3, Х4, Y4:Integer) ;

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

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

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

procedure Ellipse (XI, Yl, X2, Y2 : Integer);

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

procedure LineTo(X, Y: Integer) ;

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

procedure MoveTo (X,Y: Integer);

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

procedure BrushCopy (const Dest: 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 StretchDraw (const Rect: TRect; Graphic: TGraphic) ;

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

procedure DrawFocusRect (const Rect: TRect);

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

procedure FloodFill (X, Y: Integer; Color: TColor; FillStyle: Tf illStyle) ; TFillStyle = (fsSurface, fsBorder);

Производит заливку области текущей кистью. Процесс начинается с точки (X, Y). Если режим FillStyle равен fsSurface, то он продолжается до тех пор, пока есть соседние точки с цветом Color. В режиме fsBorder закрашивание, наоборот, прекращается при выходе на границу с цветом Color

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

Рисует сектор эллипса, описываемого прямоугольником (Х1, Y1) — (Х2, Y2). Стороны сектора лежат на лучах, проходящих из центра эллипса через точки (ХЗ, Y3) и (Х4, 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 (XI, Yl, X2, Y2: Integer);

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

procedure RoundRect (XI, Yl, X2, Y2, X3, Y3: Integer) ;

Рисует прямоугольник с закругленными углами. Координаты вершин — те же, что и в методе Rectangle. Закругления рисуются как сегменты эллипса с размерами осей по горизонтали и вертикали ХЗ и 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, отсекается и не будет видна

Таблица 10.5. Свойства класса TCanvas

Свойство

Описание

property ClipRect: TRect;

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

property Per.Pos : TPoint;

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

Метод

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 показан на рис. 10.1.

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

Где же найти ту канву, на которой предстоит рисовать? Во-первых, ею снабжены все потомки классов 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 (рис. 10.2). Если тип файла снова поменялся и стал равным 1, нужно эти компоненты убрать.

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

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

 

Класс TClipboard


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

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

procedure Open; procedure Close;

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

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

procedure Clear;

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

 property FormatCount: Integer;

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

 property Formats[Index: Integer]: Word;

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

 function HasFormat(Format: Word): Boolean.

Проверяет, содержится ли в данный момент формат Format.

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

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

procedure Assign(Source: TPersistent);

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

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

MyImage.Picture.Assign(Clipboard);

Clipboard.Assign(MyImage.Picture);

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

 function GetTextBuf(Buffer: PChar; BufSize: Integer): Integer;

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

 procedure SetTextBuf(Buffer: PChar);

Помещает текст из 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 := crDefault;

 end;

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

property Cursors[Index: Integer]: HCURSOR;

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

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

procedure TForml.FormCreate(Sender: TObj ect);

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 поставляется модуль MULTIMON.PAS, содержащий прототипы структур и функций Windows 98, 2000 для работы со многими мониторами.

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

property PixelsPerlnch: Integer;

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

property Forms[Index: Integer]: TForm;

property FormCount: Integer;

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

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

property ActiveControl: TWinControl;

property ActlveForm: TForm;

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

property OnActiveControlChange: TNotifyEvent;

property OnActiveFormChange: TNotifyEvent;

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

property DefaultKbLayout: HKL;

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

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

var RusLayout, EngLayout : THandle;

procedure TMainForm.FormCreate(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 = LANG_RUSSIAN then

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

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

if RusLayout<>0 then ActivateKeyboardLayout(RusLayout,0); 

end;

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

procedure TMainForm.EditDocSerEnter(Sender: TObject); 

begin

if EngLayout<>0 then ActivateKeyboardLayout(EngLayout, 0);

 end;

procedure TMainForm.EditDocSerExit(Sender: TObject}; 

begin

if RusLayout<>0 then ActivateKeyboardLayout(RusLayout,0);

 end;

 

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


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

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

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

Var Memory: pByteArray; 

ес : Integer;

procedure TForml.OpenlClick(Sender: TObject);

 var

i: integer;

bmFile : pBitmapFileHeader; 

bmlnfo : pBitmapInfoHeader;

 begin if not OpenDialogl.execute then Exit;

hf := CreateFile(pChar(OpenDialogl.FileName), GENERIC_READ or GENERIC_WRITE,

FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0) ; if hf=INVALID_HANDLE_VALUE then 

begin

ec:=GetLastError;

ShowMessage(' File opening error Ч-IntTostr (ec) ) ; Exit;

 end;

hm := CreateFileMapping(hf,' nil, PAGE_READONLY, 0,0,nil);

if hm=0 then

 begin

ShowMessage(' File Mapping error %d',[GetLastError]);

 Expend;

pb := MapViewOfFile(hm, FILE_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:=@(рb^[bmFile^.bfOffBits]);

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

StrLen:=(((bmInfo~.biWidth*bmInfoA.biBitCount)

+31) div 32}*4;

PaintMe(Self);

 end;

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

procedure TForml.PaintMe(Sender: TObject);

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

StretchDIBits(PaintBox.Canvas.Handle,

0,0,PaintBox.Height,PaintBox.Width,

0,0,biWidth,Abs(biHeight),

Memory, pBitmapInfo(bminfo)^, DIB_RGB_COLORS,

PaintBox.Canvas.CopyMode);

vmlxl:

with bminfoA,PaintBox.ClientRect do

i := SetDIBitsToDevice

(PaintBox.Canvas.Handle,Left,Top,Right-Left,

Bottom-Top,

Left,Top,Top,Bottom-top, 

Memory, pBitmapInfо(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;

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

end;

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

end;

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

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

 

Класс TAnimate


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

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

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

 файл (с расширением avi). Его имя нужно задать в свойстве:

property FileName: TFileName; 

 ресурс Windows. Он может быть задан одним из трех свойств:

property ResHandle: THandle; 

property ResID: Integer; 

property ResName: string;

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

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

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

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

Что особенно удобно, компонент TAnimate снимает проблемы синхронизации показа ролика с другими процессами в системе и вашем приложении.

Если свойство Timers равно значению False, показ ролика происходит в отдельном программном потоке и никак не влияет на остальное; если оно равно значению True, ролик синхронизируется по внутреннему таймеру. Вы можете привязать к показу ролика, например, проигрывание звука.

 

и не самый простой раздел




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