Юрий Спектор
Автор: Юрий Спектор, Королевство Delphi
Система Windows предоставляет программистам множество различных функций для работы с регионами, однако сколько бы их не было, всегда хочется сделать что-нибудь, не предусмотренное в стандартном наборе функций API. Кроме того, в Delphi нет класса, инкапсулирующего регионы Windows. В данной статье мы постараемся исправить эту чудовищную несправедливость.
В первой части статьи мы создадим несколько функций и процедур, расширяющие возможность стандартных API функций, а во второй - подведем итог в классе TRegion.
Для начала создадим новый модуль, назовем его ExRegions.pas
unit ExRegions;
interface
uses Windows, Classes, SysUtils, Graphics;
function CopyRgn(Reg: HRGN): HRGN;
procedure ClipRgn(Reg: HRGN);
implementation
end.
| |
Теперь напишем реализации этих функций:
- CopyRgn - Создание копии региона
- Эта функция действительно очень проста - всего две строчки кода. Сначала создаем пустой регион, потом объединяем его с копируемым функцией CombineRgn. Но мы все же оформим ее в качестве отдельной функции, отчасти для разогрева, а отчасти оттого, что будем еще неоднократно пользоваться ей.
function CopyRgn(Reg: HRGN): HRGN;
begin
Result:=CreateRectRgn(0,0,0,0);
CombineRgn(Result,Reg,Reg,RGN_COPY);
end;
| |
- ClipRgn - Обрезание региона по осям координат
- Данная процедура не сложнее предыдущей, но она тоже понадобится нам в дальнейшем. Суть ее в том, что из региона вырезается только та часть, которая на координатной плоскости находится в первой четверти (x - положительный, y - положительный).
procedure ClipRgn(Reg: HRGN);
var
R: TRect;
BoxReg: HRGN;
begin
GetRgnBox(Reg,R);
BoxReg:=CreateRectRgn(0,0,R.Right,R.Bottom);
CombineRgn(Reg,BoxReg,Reg,RGN_AND);
DeleteObject(BoxReg);
end;
| |
Под преобразованием мы будем понимать поворот, наклон, отображение, масштабирование и комбинацию этих преобразований. Для того, чтобы преобразовать регион, воспользуемся функцией API
Function ExtCreateRegion(XForm: PXForm; Count: DWORD;
const RgnData: TRgnData): HRGN;
XForm - XForm: указатель на структуру TXForm, задающую преобразование
TXForm = record
eM11: Single;
eM12: Single;
eM21: Single;
eM22: Single;
eDx: Single;
eDy: Single;
end;
| |
Вспомним математику: для того, чтобы получить новый вектор координат, нужно исходный вектор умножить на матрицу преобразования, которая выглядит следующим образом:
/ eM11 eM12 0 /
/ eM21 eM22 0 /
/ eDx eDy 1 /
Таким образом, новые координаты точки:
x' = x * eM11 + y * eM21 + eDx
y' = x * eM12 + y * eM22 + eDy
Но считать вручную нам это не придется, наша задача - это правильно составить матрицу, и передать указатель на нее в функцию.
- Count: размер в байтах, структуры передаваемой в параметре RgnData.
- RgnData: указывает на структуру типа TRgnData , которая содержит данные области. Получить их можно с помощью функции GetRegionData
function GetRegionData(RGN: HRGN; Count: DWORD; PData: PRgnData): DWORD;
- RGN: идентифицирует регион.
- Count: размер в байтах, структуры передаваемой в параметре PData.
- PData: указатель на структуру TRgnData, которая принимает информацию. Если значение этого параметра равно nil, то возвращаемое значение содержит число байт, необходимых для данных области.
Со структурой TRgnData мы разберемся чуть позже, пока она нам не потребуется. Главное это то, что в ней содержится вся необходимая информация о регионе.
interface
. . .
type
TMatrix = TXForm;
PMatrix = PXForm;
. . .
procedure TransformRgn(Reg: HRGN; Matrix: PMatrix);
. . .
implementation
. . .
procedure TransformRgn(Reg: HRGN; Matrix: PMatrix);
var
Data: Pointer;
Size: Integer;
TransReg: HRGN;
begin
Size:=GetRegionData(Reg,0,nil);
if Size>0 then begin
GetMem(Data,Size);
if GetRegionData(Reg,Size,Data)<>0 then begin
TransReg:=ExtCreateRegion(Matrix,Size,PRgnData(Data)^);
CombineRgn(Reg,TransReg,TransReg,RGN_COPY);
DeleteObject(TransReg);
end;
FreeMem(Data,Size);
end;
end;
. . .
end.
| |
Для масштабирования региона, воспользуемся только что написанной процедурой. Вся задача сводится к правильному заполнению матрицы преобразования. В случае масштабирования она имеет следующий вид:
/ sx 0 0 /
/ 0 sy 0 /
/ 0 0 1 /
- sx - показывает, во сколько регион будет растянут по горизонтали. Если 1, то регион не будет растягиваться, если меньше 1 - то регион сожмется.
- sy - аналогично, но по вертикали.
interface
. . .
procedure ScaleRgn(Reg: HRGN; sx,sy: Single);
. . .
implementation
. . .
procedure ScaleRgn(Reg: HRGN; sx,sy: Single);
var
Matrix: TMatrix;
begin
Matrix.eM11:=sx;
Matrix.eM12:=0;
Matrix.eM21:=0;
Matrix.eM22:=sy;
Matrix.eDx:=0;
Matrix.eDy:=0;
TransformRgn(Reg,@Matrix);
end;
. . .
end.
| |
Тут все просто, и в комментариях не нуждается. В прилагаемом файле, Вы сможете найти аналогичные функции для поворота, наклона и отображения регионов. Для экономии места их код в статье я приводить не буду.
А теперь усложним задачу. Следующая процедура будет не просто масштабировать регион, а подгонять его размер таким образом, чтобы он ограничивался указанным в качестве параметра прямоугольником. Кроме того, в случае необходимости, перед преобразованием будем обрезать его процедурой ClipRgn.
interface
. . .
procedure StretchRgn(Reg: HRGN; Rect: TRect; Clip: boolean);
. . .
implementation
. . .
procedure StretchRgn(Reg: HRGN; Rect: TRect; Clip: boolean);
var
RWidth, RHeight, Width, Height: integer;
R: TRect;
sx,sy: Single;
begin
if Clip then ClipRgn(Reg);
GetRgnBox(Reg,R);
RWidth:=R.Right-R.Left; RHeight:=R.Bottom-R.Top;
Width:=Rect.Right-Rect.Left;
Height:=Rect.Bottom-Rect.Top;
sx:=1; sy:=1;
if (RWidth<>0) and (RHeight<>0) then begin
sx:=Width/RWidth;
sy:=Height/RHeight;
end;
ScaleRgn(Reg,sx,sy);
GetRgnBox(Reg,R);
OffsetRgn(Reg,Rect.Left-R.Left,Rect.Top-R.Top);
end;
. . .
end.
| |
В файле, прилагаемом к статье вы найдете процедуры
procedure StretchFillRgn(DC: HDC; Reg: HRGN; Brush: HBrush; Rect: TRect;
Clip: boolean);
procedure StretchFrameRgn(DC: HDC; Reg: HRGN; Brush: HBrush; Rect: TRect;
Width: integer; Clip: boolean);
Эти процедуры понадобятся нам далее. Мы не будем рассматривать их реализацию, она очень проста. Сначала вызываем StretchRgn, а потом рисуем регион функциями FillRgn и FrameRgn соответственно.
Допустим, нам нужно создать какой-то сложный регион. Для этой цели было бы удобно использовать точечный рисунок, цвет левого верхнего пикселя в котором считался бы прозрачным, а точки другого цвета вошли бы в регион. Вообще эта задача типичная, и количество возможных реализаций очень много. Самый очевидный, но не самый лучший способ - это перебор всех точек рисунка и, если их цвет отличен от прозрачного - присоединение к региону с помощью CombineRgn. Еще вариант - записать Path и преобразовать его в регион функцией PathToRegion. Но самый оптимальный (но не самый простой) на мой взгляд вариант - это заполнение TRgnData и создание региона с помощью функции ExtCreateRgn (мы использовали ее для преобразований регионов). Реализация этой идеи была частично позаимствована мной из статьи Антона Григорьева Библиотека компонент FormRgn (создание окон непрямоугольной формы).
Сначала разберемся со структурой TRgnData:
TRgnData = record
rdh: TRgnDataHeader;
Buffer: array[0..0] of CHAR;
Reserved: array[0..2] of CHAR;
end;
Структура содержит заголовок и массив прямоугольников, которые формируют регион.
- rdh - Заголовок, имеющий тип TRgnDataHeader
- Buffer - Собственно, массив прямоугольников
- Reserved - Не используется
Теперь структура TRgnDataHeader:
TRgnDataHeader = record
dwSize: DWORD;
iType: DWORD;
nCount: DWORD;
nRgnSize: DWORD;
rcBound: TRect;
end;
- dwSize: Определяет размер заголовка, в байтах. При заполнении необходимо присвоить значение SizeOf(TRgnDataHeader)
- iType: Определяет тип области. Эта величина должна быть RDH_RECTANGLES.
- nCount: Определяет количество прямоугольников, которые создают область.
- nRgnSize: Определяет размер буфера. Если размер неизвестен, этот элемент может быть нулевым.
- rcBound: Определяет ограничение размеров области. Также может быть нулевым.
Итак, что нам нужно сделать:
- Заполнить заголовок структуры TRgnData
- Заполнить буфер структуры TRgnData
- С помощью функции ExtCreateRegion создать регион.
И еще немного теории. У класса TBitmap есть свойство ScanLine, которое позволяет получить указатель на произвольную строку в точечном рисунке. Точнее говоря не на строку, а на первую точку в строке. Изменив значение этого указателя, мы можем получить доступ к произвольной точке, чтобы определить ее цвет. Это гораздо эффективнее, чем каждый раз вызывать Canvas.Pixels или GetPixel.
interface
. . .
function CreateRgnFromBitmap(Bitmap: TBitmap): HRGN;
. . .
implementation
. . .
function CreateRgnFromBitmap(Bitmap: TBitmap): HRGN;
const
dCount = 500;
var
PLine: Pointer;
PPixel: PLongint;
DataMem: PRgnData;
H: THandle;
MaxRects: DWORD;
X,StartX,FinishX,Y: integer;
TransColor: TColor;
TransR,TransG,TransB: Byte;
TempBitmap: TBitmap;
function IsTrans(Pixel: Longint): boolean;
var
R,G,B: Byte;
begin
R:=GetBValue(Pixel);
G:=GetGValue(Pixel);
B:=GetRValue(Pixel);
Result:=(TransR = R) and (TransG = G) and (TransB = B);
end;
procedure AddRect;
var
Rect: PRect;
begin
Rect:=@DataMem^.Buffer[DataMem^.rdh.nCount*SizeOf(TRect)];
SetRect(Rect^,StartX,Y,FinishX,Y+1);
Inc(DataMem^.rdh.nCount);
end;
begin
MaxRects:=dCount;
TransColor:=GetPixel(Bitmap.Canvas.Handle,0,0);
TransR:=GetRValue(TransColor);
TransG:=GetGValue(TransColor);
TransB:=GetBValue(TransColor);
TempBitmap:=TBitmap.Create;
TempBitmap.Assign(Bitmap);
TempBitmap.PixelFormat:=pf24bit;
H:=GlobalAlloc(GMEM_MOVEABLE,SizeOf(TRgnDataHeader)+
SizeOf(TRect)*MaxRects);
DataMem:=GlobalLock(H);
ZeroMemory(@DataMem^.rdh,SizeOf(TRgnDataHeader));
DataMem^.rdh.dwSize:=SizeOf(TRgnDataHeader);
DataMem^.rdh.iType:=RDH_RECTANGLES;
for Y:=0 to TempBitmap.Height-1 do begin
PLine:=TempBitmap.ScanLine[Y];
PPixel:=PLongint(PLine);
X:=0; StartX:=0; FinishX:=0;
while X<TempBitmap.Width do begin
Inc(X);
if not IsTrans(PPixel^) then FinishX:=X
else begin
if DataMem^.rdh.nCount>=MaxRects then
begin
Inc(MaxRects,dCount);
GlobalUnlock(H);
H:=GlobalReAlloc(H,SizeOf(TRgnDataHeader)+SizeOf(TRect)*MaxRects,
GMEM_MOVEABLE);
DataMem:=GlobalLock(H);
end;
if FinishX>StartX then AddRect;
StartX:=X;
FinishX:=X;
end;
Inc(PByte(PPixel),3);
end;
if FinishX>StartX then AddRect;
end;
TempBitmap.Free;
try
Result:=ExtCreateRegion(nil,SizeOf(TRgnDataHeader)+
SizeOf(TRect)*DataMem^.rdh.nCount,DataMem^);
finally
GlobalFree(H);
end;
end;
. . .
end.
| |
Ну вот мы и подошли к самому главному. Теперь мы создадим класс-оболочку над регионом. Он будет потомком абстрактного класса TGraphic, как и битмап, иконка и метафайл. Это даст нам возможность связать TRegion с классом TPicture, а это, в свою очередь, позволит нам рисовать изображение региона на элементе Image, загружать регион с помощью стандартного диалога TOpenPictureDialog. Кроме того, если свойство какого-либо Вашего компонента будет иметь тип TRegion, оно правильно сохранится и загрузится из dfm-файла.
Реализация нашего класса будет во многом схожа с реализацией класса TIcon из модуля Graphics.pas.
Класс TGraphic, как уже было сказано, является абстрактным. Чтобы создать полноправного потомка этого класса, необходимо перекрыть все его абстрактные методы.
TGraphic = class(TInterfacedPersistent, IStreamPersist)
private
FOnChange: TNotifyEvent;
FOnProgress: TProgressEvent;
FModified: Boolean;
FTransparent: Boolean;
FPaletteModified: Boolean;
procedure SetModified(Value: Boolean);
protected
procedure Changed(Sender: TObject); virtual;
procedure DefineProperties(Filer: TFiler); override;
procedure Draw(ACanvas: TCanvas; const Rect: TRect); virtual; abstract;
function Equals(Graphic: TGraphic): Boolean; virtual;
function GetEmpty: Boolean; virtual; abstract;
function GetHeight: Integer; virtual; abstract;
function GetPalette: HPALETTE; virtual;
function GetTransparent: Boolean; virtual;
function GetWidth: Integer; virtual; abstract;
procedure Progress(Sender: TObject; Stage: TProgressStage;
PercentDone: Byte; RedrawNow: Boolean; const R: TRect;
const Msg: string); dynamic;
procedure ReadData(Stream: TStream); virtual;
procedure SetHeight(Value: Integer); virtual; abstract;
procedure SetPalette(Value: HPALETTE); virtual;
procedure SetTransparent(Value: Boolean); virtual;
procedure SetWidth(Value: Integer); virtual; abstract;
procedure WriteData(Stream: TStream); virtual;
public
constructor Create; virtual;
procedure LoadFromFile(const Filename: string); virtual;
procedure SaveToFile(const Filename: string); virtual;
procedure LoadFromStream(Stream: TStream); virtual; abstract;
procedure SaveToStream(Stream: TStream); virtual; abstract;
procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle;
APalette: HPALETTE); virtual; abstract;
procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
var APalette: HPALETTE); virtual; abstract;
property Empty: Boolean read GetEmpty;
property Height: Integer read GetHeight write SetHeight;
property Modified: Boolean read FModified write SetModified;
property Palette: HPALETTE read GetPalette write SetPalette;
property PaletteModified: Boolean read FPaletteModified write FPaletteModified;
property Transparent: Boolean read GetTransparent write SetTransparent;
property Width: Integer read GetWidth write SetWidth;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property OnProgress: TProgressEvent read FOnProgress write FOnProgress;
end;
| |
Посмотрите внимательно на свойства Empty (пустой), Width (ширина), Height (высота). Методы для установки и получения этих свойств - абстрактные. Кроме того методы GetTransparent и SetTransparent, с учетом того, что регион всегда прозрачен, мы также перекроем. Кроме того, вы еще обратили внимание на свойства Palette и Modified. Свойство Palette (палитра) нам не понадобится, а всю работу со свойством Modified (изменен), класс TGraphic организует сам.
Обратите еще внимание на метод Changed.
procedure TGraphic.Changed(Sender: TObject);
begin
FModified := True;
if Assigned(FOnChange) then FOnChange(Self);
end;
| |
Этот метод мы будем вызывать в том случае, если после создания, объект класса TRegion был изменен.
interface
. . .
const
SChangeRegionSize = 'Cannot change the size of a region';
. . .
type
. . .
TRegion = class(TGraphic)
protected
function GetEmpty: Boolean; override;
function GetHeight: Integer; override;
function GetWidth: Integer; override;
function GetTransparent: Boolean; override;
procedure SetHeight(Value: Integer); override;
procedure SetTransparent(Value: Boolean); override;
procedure SetWidth(Value: Integer); override;
end.
. . .
implementation
. . .
function TRegion.GetEmpty: Boolean;
begin
Result:=Handle = 0;
end;
function TRegion.GetHeight: Integer;
var
R: TRect;
begin
Result:=0;
if Handle<>0 then begin
GetRgnBox(Handle,R);
Result:=R.Bottom+1;
end;
end;
function TRegion.GetWidth: Integer;
var
R: TRect;
Begin
Result:=0;
if Handle<>0 then begin
GetRgnBox(Handle,R);
Result:=R.Right+1;
end;
end;
function TRegion.GetTransparent: Boolean;
begin
Result:=true;
end;
procedure TRegion.SetHeight(Value: Integer);
begin
raise EInvalidGraphicOperation.Create(SChangeRegionSize);
end;
procedure TRegion.SetTransparent(Value: Boolean);
begin
end;
procedure TRegion.SetWidth(Value: Integer);
begin
raise EInvalidGraphicOperation.Create(SChangeRegionSize);
end;
. . .
end.
| |
Этот механизм позволяет нескольким объектам ссылаться на один и тот же, реально существующий в системе. Свойство Handle у таких объектов будет идентично. Реализуется такое связывание через метод Assign:
Bitmap1.Assign(Bitmap2);
Стандартные потомки TGraphic, такие как TIcon, TBitmap и TMetafile имеют свои системы кэширования. В нашем случае, это не имеет большого практического смысла, так как регион, обычно, в памяти занимает гораздо меньше места, чем битмап или метафайл, да и использоваться будет реже. Но мы все равно рассмотрим этот механизм, так как идея подсчета ссылок, на которой он основывается, используется повсеместно (DLL, технология COM, длинные строки и т.д.)
Упрощенно идея заключается в следующем: Если объекта еще нет в памяти, то создаем его, а счетчик ссылок на него устанавливаем в 1. При копировании объекта методом Assign, увеличиваем счетчик ссылок на 1, а при уничтожении экземпляра класса, который на него ссылается - уменьшаем на 1. Если число ссылок стало равным 0 (и только в этом случае!!!) - уничтожаем объект и освобождаем выделенную под него память.
Для реализации этого механизма, в модуле Graphics.pas объявлен класс:
TSharedImage = class
private
FRefCount: Integer;
protected
procedure Reference;
procedure Release;
procedure FreeHandle; virtual; abstract;
property RefCount: Integer read FRefCount;
end;
| |
Создадим потомка, в котором введем поле FHandle, для хранения дескриптора региона, и перекроем метод FreeHandle. Методы Reference и Release перекрывать не нужно:
interface
. . .
TRegionImage = class(TSharedImage)
private
FHandle: HRGN;
protected
procedure FreeHandle; override;
end;
. . .
implementation
. . .
procedure TRegionImage.FreeHandle;
begin
if FHandle<>0 then begin
DeleteObject(FHandle);
FHandle:=0;
end;
end;
. . .
end.
| |
Вот и все. Теперь на реально существующий в системе регион будет ссылаться объект этого класса, а объекты класса TRegion, будут ссылаться на него. Таким образом, если несколько объектов TRegion ссылаются на один и тот же TRegionImage, мы экономим ресурсы системы, не размещая в памяти несколько одинаковых регионов.
interface
. . .
TRegion = class(TGraphic)
private
FImage: TRegionImage;
FBrush: TBrush;
FFrame: boolean;
function GetHandle: HRGN;
procedure SetHandle(const Value: HRGN);
procedure NewRegion(Reg: HRGN);
protected
. . .
public
constructor Create; override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
property Handle: HRGN read GetHandle write SetHandle;
property Brush: TBrush read FBrush write FBrush;
property Frame: boolean read FFrame write FFrame;
end;
. . .
implementation
. . .
constructor TRegion.Create;
begin
inherited Create;
FImage:=TRegionImage.Create;
FImage.Reference;
FImage.FHandle:=0;
FBrush:=TBrush.Create;
FBrush.Color:=clBlack;
FBrush.Style:=bsDiagCross;
FFrame:=true;
end;
destructor TRegion.Destroy;
begin
FBrush.Free;
FImage.Release;
inherited Destroy;
end;
procedure NewRegion(Reg: HRGN);
var
Region: TRegionImage;
begin
Region:=TRegionImage.Create;
Region.FHandle:=Reg;
Region.Reference;
FImage.Release;
FImage:=Region;
end;
procedure TRegion.SetHandle(const Value: HRGN);
begin
NewRegion(Value);
Changed(Self);
end;
function TRegion.GetHandle: HRGN;
begin
Result:=FImage.FHandle;
end;
procedure TRegion.Assign(Source: TPersistent);
begin
if (Source is TRegion) or (Source = nil) then begin
if (Source <> nil) then begin
TRegion(Source).FImage.Reference;
FImage.Release;
FImage:=TRegion(Source).FImage;
Brush.Assign((Source as TRegion).Brush);
Frame:=(Source as TRegion).Frame;
end
else
NewRegion(0);
Changed(Self);
end
else inherited Assign(Source);
end;
. . .
end.
| |
Для этого в классе TGraphic предусмотрены методы DefineProperties, ReadData, WriteData, LoadFromStream, SaveToStream, LoadFromFile, SaveToFile.
В нашем классе мы перекроем все методы, кроме SaveToFile, LoadToFile и DefineProperties, поэтому их реализацию в классе TGraphic мы сейчас рассмотрим.
procedure TGraphic.DefineProperties(Filer: TFiler);
function DoWrite: Boolean;
begin
if Filer.Ancestor <> nil then
Result := not (Filer.Ancestor is TGraphic) or
not Equals(TGraphic(Filer.Ancestor))
else
Result := not Empty;
end;
begin
Filer.DefineBinaryProperty('Data', ReadData, WriteData, DoWrite);
end;
| |
Этот метод нужен для сохранения данных об объекте в dfm-файл. Это позволит нам, при создании собственных компонент, задавать в design-time свойства типа TRegion. Не пугайтесь, если в вышеприведенном коде много незнакомого. Самое главное это то, что запись свойств производится методом WriteData, а чтение - ReadData. А сами методы мы еще напишем.
procedure TGraphic.LoadFromFile(const Filename: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure TGraphic.SaveToFile(const Filename: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(Filename, fmCreate);
try
SaveToStream(Stream);
finally
Stream.Free;
end;
end;
| |
Тут все проще - запись в файл - это запись в поток, чтение из файла - чтение из потока. Вот только методы SaveToStream и LoadFromStream - абстрактные.
Подведем итоги: нам нужно перекрыть методы ReadData, WriteData, LoadFromStream и SaveToStream.
interface
. . .
TRegion = class(TGraphic)
private
. . .
procedure ReadStream(Stream: TStream; Size: Longint);
procedure WriteStream(Stream: TStream; WriteSize: Boolean);
protected
. . .
procedure ReadData(Stream: TStream); override;
procedure WriteData(Stream: TStream); override;
public
. . .
procedure LoadFromStream(Stream: TStream); override;
procedure SaveToStream(Stream: TStream); override;
end.
| |
Прежде, чем перейти к рассмотрению реализации этих методов, определимся, в каком формате мы будем хранить данные о регионе в файле.
Данные - это то, что возвращает функция GetRegionData. В начале будет стоять заголовок файла, который включает слово Region - признак файла региона и размер данных, также полученный с помощью функции GetRegionData. Сами данные, которые, как уже было сказано, включают заголовок и буфер.
interface
. . .
const
. . .
CapSize = 6;
. . .
type
. . .
THeaderCaption = array[1..CapSize] of Char;
TRegionHeader = record
Caption: THeaderCaption;
Size: Integer;
end;
. . .
const
HeaderCaption: THeaderCaption = 'Region';
. . .
implementation
. . .
procedure TRegion.ReadStream(Stream: TStream; Size: Integer);
var
Header: TRegionHeader;
Buf: Pointer;
FSize: DWORD;
begin
if Size>0 then begin
Stream.ReadBuffer(Header,SizeOf(Header));
FSize:=Header.Size;
if (Header.Caption = HeaderCaption) and (FSize>0) then begin
GetMem(Buf,FSize);
Stream.ReadBuffer(Buf^,FSize);
try
Handle:=ExtCreateRegion(nil,FSize,PRgnData(Buf)^);
finally
FreeMem(Buf,FSize);
end;
end;
end;
end;
procedure TRegion.WriteStream(Stream: TStream; WriteSize: Boolean);
var
Header: TRegionHeader;
WSize, Size: DWORD;
Buf: Pointer;
begin
if Handle<>0 then begin
Header.Caption:=HeaderCaption;
Size:=GetRegionData(Handle,0,nil);
if Size>0 then begin
Header.Size:=Size;
GetMem(Buf,Size);
GetRegionData(Handle,Size,PRgnData(Buf));
if WriteSize then begin
WSize:=DWORD(SizeOf(Header)+Size);
Stream.WriteBuffer(WSize,SizeOf(DWORD));
end;
Stream.WriteBuffer(Header,SizeOf(Header));
Stream.WriteBuffer(Buf^,Size);
FreeMem(Buf,Size);
end;
end;
end;
procedure TRegion.ReadData(Stream: TStream);
var
Size: DWORD;
Begin
Stream.ReadBuffer(Size,SizeOf(DWORD));
ReadStream(Stream,Size);
end;
procedure TRegion.WriteData(Stream: TStream);
begin
WriteStream(Stream,true);
end;
procedure TRegion.LoadFromStream(Stream: TStream);
begin
ReadStream(Stream, Stream.Size - Stream.Position);
end;
procedure TRegion.SaveToStream(Stream: TStream);
begin
WriteStream(Stream, False);
end;
. . .
end.
| |
interface
. . .
const
. . .
SRegionToClipboard = 'Clipboard does not support Regions';
. . .
type
. . .
TRegion = class(TGraphic)
. . .
protected
. . .
procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;
public
. . .
procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle;
APalette: HPALETTE); override;
procedure SaveToClipboardFormat(var Format: Word; var Data: THandle;
var APalette: HPALETTE); override;
procedure ImportFromBitmap(Bitmap: TBitmap);
end.
| |
Метод Draw - масштабирует и рисует регион на канве. Метод ImportFromBitmap, как несложно догадаться, создает регион из точечного рисунка и заставляет наш объект ссылаться на него. Остальные методы дают возможность (а точнее, не дают, но Вы можете это исправить) работать нашему объекту с буфером обмена.
implementation
. . .
procedure TRegion.Draw(ACanvas: TCanvas; const Rect: TRect);
var
BlackBrush: HBrush;
Begin
StretchFillRgn(ACanvas.Handle,Handle,Brush.Handle,Rect,true);
if Frame then begin
BlackBrush:=GetStockObject(Black_Brush);
StretchFrameRgn(ACanvas.Handle,Handle,BlackBrush,Rect,1,true);
end;
end;
procedure TRegion.ImportFromBitmap(Bitmap: TBitmap);
begin
Handle:=CreateRgnFromBitmap(Bitmap);
end;
procedure TRegion.LoadFromClipboardFormat(AFormat: Word; AData: THandle;
APalette: HPALETTE);
begin
raise EInvalidGraphicOperation.Create(SRegionToClipboard);
end;
procedure TRegion.SaveToClipboardFormat(var Format: Word;
var Data: THandle; var APalette: HPALETTE);
begin
raise EInvalidGraphicOperation.Create(SRegionToClipboard);
end;
. . .
end.
| |
Теперь осталось только сделать так, чтобы в объект класса TPicture можно было поместить нашу картинку-регион.
interface
. . .
const
. . .
SVRegions = 'Regions';
. . .
initialization
TPicture.RegisterFileFormat('rgn',SVRegions,TRegion);
finalization
TPicture.UnregisterGraphicClass(TRegion);
end.
| |
Вот и все! Теперь Вы можете:
- Импортировать регионы из точечных рисунков
- Хранить регионы в файлах, ресурсах
- Создавать свои компоненты со свойствами типа TRegion
- Открывать файл регионов с помощью диалога TOpenPictureDialog
- Рисовать регион на канве методом Draw
- Загружать картинку-регион в TImage.
В прилагаемом к статье архиве, Вы сможете найти модуль ExRegions.pas, программу, демонстрирующую его работу и несколько файлов *.rgn.
Ссылки по теме
Файлы для загрузки