Direct2D: Brushes Overview #2
Опубликовано 22.05.2010 г. 19:42
В этой статье мы рассмотрим применение последнего типа кистей - ID2D1BitmapBrush, а также, как итог, нарисуем полноэкранную форму с вращающейся картинкой-фоном.
что такое растровая кисть и как с ней обращаться?
Очевидно, что растровая кисть заполняет требуемую область с помощью некоторого изображения. Причем сразу скажем, если размер кисти меньше области изображения, то мы получим одну копию нашей картинки в точке (0,0), остальная же часть поверхности останется нетронутой. Чтобы заполнить всю область применяются расширенные режимы заполнения. Таким образом мы можем повторять изображение по осям Х и У, или зеркально отображать, либо просто растягивать. (Иллюстрация к режимам заполнения) В статье Creating a Simple Direct2D Application наша форма имела в качестве фона решетку. При этом решетка рисовалась с помощью цикла, в котором чертились вертикальные, а потом горизонтальные линии. Конечно же такой способ заливки фона совсем не удобен. Куда логичней было бы залить фон рисунком содержащим одну клеточку. И тогда нам не понадобится никаких циклов, потребуется лишь кисть, рисующая клетку, с повторением по обеим осям координат. Далее, давайте поверх сетки нарисуем уже какой нить действительный рисунок, для этого я выбрал свою аватару (: Рисунок будет полупрозрачный, так что сетка будет просвечивать, а с угла в угол формы нарисуем градиент, который в начале будет прозрачным, и в конце наоборот. Для интересу давайте добавим на форму таймер, который при срабатывании будет перерисовывать форму, при этом поворачивать наш рисунок. Теперь давайте посмотрим описание формы:TMainForm = class(TForm) Timer: TTimer; procedure FormPaint(Sender: TObject); procedure FormResize(Sender: TObject); procedure FormCreate(Sender: TObject); procedure TimerTimer(Sender: TObject); private { Private declarations } fCanvas : TDirect2DCanvas; fBgBrush : ID2D1BitmapBrush; fImgBrush: ID2D1BitmapBrush; fLgBrush : ID2D1LinearGradientBrush; fImgAngle : single; procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND; procedure CreateBackgroundBrush(); procedure CreateBitmapBrush(); procedure CreateLinearGradientBrush(); public property Canvas : TDirect2DCanvas read fCanvas; end;метод FormPaint осуществляет все операции рисования на канве, методы onResize & TimerTimer перерисовывают содержимое канвы, в событии FormCreate мы определим поддерживается ли Direct2D в системе, создадим нужные нам кисти. В раздел private добавим три переменные определяющие наши кисти: фон (fBgBrush), изображение (fImgBrush), градиент (fLgBrush), а также добавим три процедуры для создания этих кистей. Переменная fImgAngle будет определять текущий угол поворота изображения. Рассмотрим процедуру создания формы:
procedure TMainForm.FormCreate(Sender: TObject); begin if not TDirect2DCanvas.Supported then begin MessageDlg('D2D is not supported!', mtError,[mbOK],0); Application.Terminate; end; fCanvas := TDirect2DCanvas.Create(handle); (Canvas.RenderTarget as ID2D1HwndRenderTarget).setDPI(96,96); CreateBackgroundBrush(); CreateBitmapBrush(); CreateLinearGradientBrush(); setWindowLong(handle, GWL_STYLE, WS_POPUP and WS_VISIBLE); left := 0; top :=0; width := screen.Width; height := screen.Height; fImgAngle := 0; end;Сначала определим поддерживается ли технология Direct2D. Далее создадим D2D канву. После чего вызовем методы для создания кистей. Развернем приложение на полный экран с помощью функции setWindowLong. Начальным значением угла поворота будет 0. А теперь рассмотрим процедуры создания кистей.
Фоновая кисть
Как уже говорилось выше, чтобы заполнить фон формы решеткой, нам достаточно создать кисть содержащую одну ячейку данной решетки, и с помощью ее заполнить наш фон. Мы не будем пользоваться готовым изображением решетки, а нарисуем ее самостоятельно. Для этого:- создадим растровую поверхность bmRt : ID2D1BitmapRenderTarget, совместимую с нашей канвой, размера 10х10 пикселей
- Установим цвет кисти серым (поскольку поверхность совместимая, то мы можем использовать кисть канвы)
- Зальем фон белым цветом
- Нарисуем на ней две линии, для формирования двух граней квадрата решетки. Остальные две грани будут получены при повторении изображения по осям Х и У.
- Получим объект ID2D1Bitmap содержащий изображение поверхности
- заполним структуру TD2D1BitmapBrushProperties для установки метода заполнения поверхности за рамками кисти
- с помощью поверхности создадим кисть с полученным изображением в переменную fBgBrush
- Установим прозрачность кисти в 1 (полностью непрозрачный)
procedure TMainForm.CreateBackGroundBrush; var bmRt : ID2D1BitmapRenderTarget; sf : TD2D1SizeF; bgBitmap : ID2D1Bitmap; bgBrushProperties : TD2D1BitmapBrushProperties; begin sf := D2D1SizeF(10,10); with canvas do begin RenderTarget.CreateCompatibleRenderTarget(@sf,nil,nil,0,bmRt); Brush.Color := clGray; end; with bmRt do begin BeginDraw; Clear(D2D1ColorF(1,1,1,1)); DrawLine(point(0,0), point(0,10), canvas.Brush.Handle); DrawLine(point(0,0), point(10,0), canvas.Brush.Handle); EndDraw(); GetBitmap(bgBitmap); end; bgBrushProperties.extendModeX := D2D1_EXTEND_MODE_WRAP; bgBrushProperties.extendModeY := D2D1_EXTEND_MODE_WRAP; bgBrushProperties.interpolationMode := 0; canvas.renderTarget.CreateBitmapBrush(bgBitmap,@bgBrushProperties,nil,fBgBrush); fBgBrush.SetOpacity(1); end;
Кисть с изображением
В поле fImgBrush формы мы будем хранить кисть с изображением, которое будем рисовать поверх нашей сетки. В отличии от фоновой кисти, теперь мы не будем рисовать его, а загрузим из файла, вернее из ресурса. Сначала подберем какую нить картинку формата BMP. Добавим ее к ресурсам приложения: Project->Resources. И установим идентификатор ресурса "ImgBrush". Что бы создать растровую кисть будем следовать следующему алгоритму:- С помощью объекта TBitmap загрузим наше изображение из ресурсов.
- Чтобы получить изображение ID2D1Bitmap воспользуемся методом TDirect2DCanvas.CreateBitmap
- Установим режимы заполнения канвы за границами кисти, для повторения рисунка по обеим осям
- Создам кисть с помощью поверхности
- Установим прозрачность кисти 0.5, чтобы была видна сетка нашего фона
procedure TMainForm.CreateBitmapBrush; var bm : TBitmap; bitmap : ID2D1Bitmap; imgBrushProperties : TD2D1BitmapBrushProperties; begin bm := TBitmap.Create; bm.LoadFromResourceName(HInstance,'ImgBrush'); bitmap := canvas.CreateBitmap(bm); bm.free; imgBrushProperties.extendModeX := D2D1_EXTEND_MODE_WRAP; imgBrushProperties.extendModeY := D2D1_EXTEND_MODE_WRAp; imgBrushProperties.interpolationMode := 0; canvas.renderTarget.CreateBitmapBrush(Bitmap, @imgBrushProperties, nil, fImgBrush); fImgBrush.SetOpacity(0.5); end;Из намеченных трех кистей, осталась одна.
линейный градиент с прозрачностью
Создание подобных кистей уже рассматривалось подробно в предыдущей статье, поэтому здесь только отметим пару моментов. Линейным градиентом будем заливать форму из верхнего левого угла в нижний правый. Цвет на обоих концах градиента будет одинаковый - белый. При этом в левом верхнем углу цвет будет прозрачным, в противоположном - непрозрачным. Таким образом нарисовав подобный градиент после решетки и изображения, мы как бы сотрем немного правый нижний угол. При создании кисти не установим конечную точку равной начальной, т.к. конечную точку нам придется изменять вместе с изменением размера формы. Приведем код создания кисти и процедуры изменения размера формы:procedure TMainForm.CreateLinearGradientBrush; var gStops : array[0..1] of TD2D1GradientStop; gsCollection : ID2D1GradientStopCollection; begin gStops[0].position := 0; gStops[0].color := D2D1ColorF(clWhite,0); gStops[1].position := 1; gStops[1].color := D2D1ColorF(clWhite,1); with canvas.RenderTarget do begin CreateGradientStopCollection(@gStops, 2, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, gsCollection); CreateLinearGradientBrush(D2D1LinearGradientBrushProperties(point(0,0), point(0,0)), nil, gsCollection, fLgBrush); end; end; procedure TMainForm.FormResize(Sender: TObject); var bitmap : ID2D1Bitmap; begin (fCanvas.RenderTarget as ID2D1HwndRenderTarget).Resize(D2D1SizeU(clientWidth,clientHeight)); fLgBrush.SetEndPoint(getClientRect().BottomRight); invalidate(); end;
Процедура рисования на канве
После того как мы подготовили все три кисти которые собирались использовать, можем описать процедуру рисования на канве. Она будет достаточно проста:- сохраним размер окна, чтобы не вызывать постоянно метод getClientRect()
- Установим фоновую кисть с решеткой, и закрасим ей клиентскую область
- Т.к мы договорились поворачивать рисунок, то увеличим угол поворота fImgAngle и применим трансформацию кисти - поворот на заданный угол, относительно точки (30,30). Применим кисть, и заполним ей клиентскую область
- Применим градиентную кисть и также заполним область
- Чтобы было интересней, давайте нарисуем произвольное количество квадратиков произвольного размера, произвольного цвета и прозрачности, в произвольной точке, для чего определим вспомогательную функцию getRandomPoint().
procedure TMainForm.FormPaint(Sender: TObject); var cr : TRect; p : TPoint; size : byte; pCount : byte; function getRandomPoint():TPoint; begin size := random(100); result.x := random(cr.Right - size); result.y := random(cr.Bottom - size); end; begin cr := getClientRect(); with canvas do begin beginDraw(); brush.Handle := fBgBrush; FillRect(cr); fImgAngle := trunc(fImgAngle + 1) mod 360; fImgBrush.SetTransform(TD2DMatrix3x2F.Rotation(fImgAngle,point(30,30))); brush.Handle := fImgBrush; FillRect(cr); brush.Handle := fLgBrush; FillRect(cr); pCount := random(30); while pCount > 0 do begin brush.Color := Random(clWhite); brush.Handle.SetOpacity(random(100)/100); p := getRandomPoint(); FillRectangle(Rect(p.x, p.y, p.x+size, p.y+size)); dec(pCount); end; EndDraw(); end; end;Как я уже говорил, перерисовка изображения формы будет происходить по таймеру, скажем через 20мс.
procedure TMainForm.TimerTimer(Sender: TObject); begin invalidate(); end;
Ну и напоследок рисунок того что вышло в разрешении 1680х1050:
Комментарии
Нет комментариев