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

Мыльные пузыри в Delphi #3. Растровая маска прозрачности

Опубликовано 23.08.2011 г. 00:05

В этот раз я попытался ускорить процесс отрисовки пузырей, ибо в прошлый все это добросовестно подтормаживало.

На самом деле даже не знаю стало ли лучше. Хотя вобще стало, при запуске загрузка процессора около 20 процентов. В прошлый раз было ближе к 40. Что изменилось: ранее для отрисовки пузыря использовались слои, как утверждает документация это сложно и медленно. А у нас в каждом кадре использовалось число слоев по количеству пузырей. Идея оптимизации была в том, что можно использовать маску прозрачности на основе изображения пузыря. Т.е. если раньше мы рисовали пузырь так: брали два эллипса (контур и вложенный), создавали кисть с прозрачностью в центре, заливали оба эллипса градиентом, с использованием маски из этой кисти с помощью слоя, то теперь рисование строится по следующему принципу. В основе пузыря всегда лежит залитый градиентом эллипс. Все что нам надо - наложить на эллипс маску прозрачности. Маска эта будет как и выше строится на основе эллипсов. Но суть в том, что маска такая создается только один раз, и просто применяется, при выводе пузыря. Т.е. слои вообще не используются в коде, даже при создании этой самой маски. Вот тестовая картинка того, как применяется маска. В данном случае растровая маска прозрачности, применена к кругу сплошной заливкой желтого цвета.
  к
ак можно заметить, качество отрисовки значительно страдает. Связано это с тем, что для использования растровой маски необходимо использовать D2D1_ANTIALIAS_MODE_ALIASED в качестве настройки сглаживания. Ведет этом к тому, что сравнение прозрачности происходит попиксельно, и на резкой границе прозрачности у нас получается весьма резкий край. Чтобы замаскировать этот эффект, после отрисовки маски, мы возвращаем режим D2D1_ANTIALIAS_MODE_PER_PRIMITIVE и отрисовываем эллипс, в результате восстанавливая грань (рисуем эллипс, а не закрашиваем). Картинка с MSDN об использовании растра в качестве маски прозрачности:

Здесь в центре нарисована маска. Черным цветом изображены пикселы у которых прозрачность равна нулю, белым - единице. Такая маска накладывается на изображение слева, и после отрисовки на канве белого цвета, получается изображение справа. Приведу полный код создания растровой маски пузыря. Отчасти он повторяет прошлый код, в моментах создания кисти с круговым градентом, и геометрий для эллипса.
        procedure CreateBubbleOpacityMask();
        var gStops : array[0..2] of TD2D1GradientStop;
            gStopsCollection : ID2D1GradientStopCollection;
            bp : TD2D1BrushProperties;
            bob : ID2D1RadialGradientBrush;
            bmrt : ID2D1BitmapRenderTarget;
            sf : TD2D1SizeF;
            geometries : array[0..1] of ID2D1EllipseGeometry;
            gg : ID2D1GeometryGroup;
        begin
            //radial brush
            gStops[0] := D2D1GradientStop(0,   D2D1ColorF(clWhite, 0));
            gStops[1] := D2D1GradientStop(0.7, D2D1ColorF(clBlack, 0.2));
            gStops[2] := D2D1GradientStop(1,   D2D1ColorF(clBlack, 1));

            FRenderTarget.CreateGradientStopCollection(@gStops, 3,
                                    D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP,
                                    gStopsCollection);

            FRenderTarget.CreateRadialGradientBrush(
                                    D2D1RadialGradientBrushProperties(point(0,0), point(0,0), 100, 100),
                                    nil, gStopsCollection, bob);

            //ellipse geometries
            with ellipse do begin
                point := D2D1PointF(0,0);
                radiusX := 100;
                radiusY := 100;
            end;

            FBubble := ellipse;
            Factory.CreateEllipseGeometry(ellipse, Geometries[0]);

            with ellipse do begin
                point := D2D1PointF(0,-33);
                radiusX := 84;
                radiusY := 65;
            end;
            Factory.CreateEllipseGeometry(ellipse, Geometries[1]);
            Factory.CreateGeometryGroup(D2D1_FILL_MODE_ALTERNATE, @Geometries,2, gg);

            // bitmap mask
            sf.width := 200;
            sf.height := 200;

            FRenderTarget.CreateCompatibleRenderTarget(@sf, nil,nil, 0, bmrt);

            bmrt.BeginDraw();
            try
                bmrt.SetTransform(TD2DMatrix3x2F.Translation(100,100));
                bob.SetOpacity(1);
                bmrt.Clear(D2D1ColorF(clWhite,0));

                bmrt.FillGeometry(gg, bob);
                bob.SetOpacity(0.5);
                bmrt.FillGeometry(geometries[1], bob);
            finally
                bmrt.EndDraw()
            end;
            bmrt.GetBitmap(FBubbleOpacityMask);
        end;
В результате мы получаем растр FBubbleOpacityMask : ID2D1Bitmap. Процесс создания ее прост. Сначала с помощью исхдной канвы мы создаем совместимую растровую канву bmrt : ID2D1BitmapRenderTarget. Рисование проводим на ней. Цвет для нас не имеет значения. Внимание стоит обращать только на прозрачность. Как и в прошлые разы заливаем сначала фигуру из двух эллипсов. Потом внутренний эллипс, уменьшив прозрачность. Нарисовав маску на канве мы получаем соответствующий растр. Теперь, чтобы нарисовать искомый пузырь поступаем следующим образом:
       mt := TD2DMatrix3x2F.Translation(c.x - 100, c.y - 100);
        FRenderTarget.SetTransform(mt);

        rc := D2D1RectF(0,0,200,200);

        FRenderTarget.SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);

        FRenderTarget.FillOpacityMask(FBubbleOpacityMask, FBubbleBrush,
                                      D2D1_OPACITY_MASK_CONTENT_GRAPHICS,
                                      @rc, @rc);

        FRenderTArget.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);

        //bubble border
        mt := TD2DMatrix3x2F.Translation(c.x, c.y);
        FRenderTarget.SetTransform(mt);
        FRenderTarget.DrawEllipse(FBubble,FBubbleBrush, 2);
меняем режим сглаживания, используя метод FillOpacityMask применяем нашу маску и кисть с линейным градентом для заливки прямоугольной области. В итоге получаем пузырь. Чтобы сделать грань более красивой рисуем эллипс, с толщиной линии в 2 пикселя. Это правда немного изменяет вид пузыря:

Но кажется выглядит неплохо. Следующим шагом могла бы стать доработка изменения цвета пузыря - ибо цвет до сих пор меняется произвольно при столкновении с гранью. Также возможна реализация алгоритма столкновения пузырей, это в общем то может представлять интерес. Помимо данных изменений был решен вопрос с падением программы при выходе (: глупый косяк (: а также все что касалось TBubble было переведено с integer/TPoint на double/TD2DPoint2F. Для интересующихся последний вариант исходного кода пузырей можно скачать здесь.
Метки:  Direct2D  |  animation 

Комментарии

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