Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4421.html
 

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. А теперь рассмотрим процедуры создания кистей.

Фоновая кисть

Как уже говорилось выше, чтобы заполнить фон формы решеткой, нам достаточно создать кисть содержащую одну ячейку данной решетки, и с помощью ее заполнить наш фон. Мы не будем пользоваться готовым изображением решетки, а нарисуем ее самостоятельно. Для этого:
  1. создадим растровую поверхность bmRt : ID2D1BitmapRenderTarget, совместимую с нашей канвой, размера 10х10 пикселей
  2. Установим цвет кисти серым (поскольку поверхность совместимая, то мы можем использовать кисть канвы)
  3. Зальем фон белым цветом
  4. Нарисуем на ней две линии, для формирования двух граней квадрата решетки. Остальные две грани будут получены при повторении изображения по осям Х и У.
  5. Получим объект ID2D1Bitmap содержащий изображение поверхности
  6. заполним структуру TD2D1BitmapBrushProperties для установки метода заполнения поверхности за рамками кисти
  7. с помощью поверхности создадим кисть с полученным изображением в переменную fBgBrush
  8. Установим прозрачность кисти в 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". Что бы создать растровую кисть будем следовать следующему алгоритму:
  1. С помощью объекта TBitmap загрузим наше изображение из ресурсов.
  2. Чтобы получить изображение ID2D1Bitmap воспользуемся методом TDirect2DCanvas.CreateBitmap
  3. Установим режимы заполнения канвы за границами кисти, для повторения рисунка по обеим осям
  4. Создам кисть с помощью поверхности
  5. Установим прозрачность кисти 0.5, чтобы была видна сетка нашего фона
Код реализующий данные шаги в функции создания кисти CreateBitmapBrush:
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;

Процедура рисования на канве

После того как мы подготовили все три кисти которые собирались использовать, можем описать процедуру рисования на канве. Она будет достаточно проста:
  1. сохраним размер окна, чтобы не вызывать постоянно метод getClientRect()
  2. Установим фоновую кисть с решеткой, и закрасим ей клиентскую область
  3. Т.к мы договорились поворачивать рисунок, то увеличим угол поворота fImgAngle и применим трансформацию кисти - поворот на заданный угол, относительно точки (30,30). Применим кисть, и заполним ей клиентскую область
  4. Применим градиентную кисть и также заполним область
  5. Чтобы было интересней, давайте нарисуем произвольное количество квадратиков произвольного размера, произвольного цвета и прозрачности, в произвольной точке, для чего определим вспомогательную функцию 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:

 

Метки:  Direct2D  |  bitmap  |  brush 

Комментарии

Нет комментариев
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно