このページでは

C

ベクターグラフィックスサポートの実装

このトピックでは、ターゲットプラットフォームがハードウェアアクセラレーションによるベクターグラフィックスサポートをサポートしている場合、それを実装する方法について説明します。

概要

ベクターパス

一連のパスコマンドを示すベクターグラフィックスの例。

ベクトル・パスは通常、一連のパス・コマンドとして表現される。例えば、先の画像のパスはSVGパスとして定義できる:

M 21,26.50 V 60 H 69 C 69,60 21,60 21,26.50 Z

M: MoveTo, V: LineTo(垂直), H: LineTo(水平), C: Cubic Bézier Curve, Z: ClosePath がパスコマンドである。数値は各コマンドの座標である。これらのコマンドは大文字なので、引数は絶対座標として扱われる。

ベクトルパス表現

プラットフォーム・インターフェースにおけるベクトル・パスは、PlatformInterface::PathData クラスによって表現されます。先ほどのパスの例では、出力は以下のようになります:

関数出力
PathData::segmentCount()5
PathData::segments()
Qul::PlatformInterface::PathData::SegmentType [5] {
    MoveSegment, LineSegment, LineSegment, CubicBezierSegment, CloseSegment
}
PathData::controlElementCount()12
PathData::controlElements()パスの例の値は、次のように格納されます:
float [12] { x1, y1, x2, y2, x3, y3, cx1, cy1, cx2, cy2, x4, y4 }

座標

float [12] { 21.0f, 26.5f, 21.0f, 60.0f, 69.0f, 60.0f, 69.0f, 60.0f, 21.0f, 60.0f, 21.0f, 26.5f }

注: 縦線と横線は、x座標とy座標の両方を持つLineSegmentとして保存されます。

グラフィックスドライバのパス例

グラフィックス・ドライバ・インターフェースの例では、int32_t をパス・コマンドと引数の両方に使用して、ベクトル・パスを表現しています。座標には S16.15 固定小数点形式を使用し、すべてのベクトル・パスはEND コマンドで終わります。パス・コマンドはあらかじめ定義された整数値です。先の例で使用したベクトル・パスは次のようになります:

int32_t [] { MOVETO, x1, y1, LINETO, x2, y2, LINETO, x3, y3, CUBIC, cx1, cy1, cx2, cy2, x4, y4, CLOSE, END }

座標はS16.15に変換されます:

int32_t [] { MOVETO, 21.0*(1<<15), 26.5*(1<<15), LINETO, 21.0*(1<<15), 60.0*(1<<15), LINETO, ... }

ベクターグラフィックスサポートの実装

描画エンジンのオーバーライド

ベクトル・グラフィックスのサポートを実装するには、PlatformInterface::DrawingEngine をサブクラスにして、以下の関数をオーバーライドします:

class ExampleDrawingEngine : public PlatformInterface::DrawingEngine
{
public:
    ...
    PlatformInterface::DrawingEngine::Path *allocatePath(const PlatformInterface::PathData *pathData,
                                                         PlatformInterface::PathFillRule fillRule) QUL_DECL_OVERRIDE;

    void setStrokeProperties(PlatformInterface::DrawingEngine::Path *path,
                             const PlatformInterface::StrokeProperties &strokeProperties) QUL_DECL_OVERRIDE;

    void blendPath(PlatformInterface::DrawingDevice *drawingDevice,
                   PlatformInterface::DrawingEngine::Path *path,
                   const PlatformInterface::Transform &transform,
                   const PlatformInterface::Rect &clipRect,
                   const PlatformInterface::Brush *fillBrush,
                   const PlatformInterface::Brush *strokeBrush,
                   int sourceOpacity,
                   PlatformInterface::DrawingEngine::BlendMode blendMode) QUL_DECL_OVERRIDE;

    PlatformInterface::RenderHints supportedRenderHints() const QUL_DECL_OVERRIDE;
};

パスの前処理

次に、プラットフォーム固有のパス・データを格納するために、PlatformInterface::DrawingEngine::Path のサブクラスを作成する。このExamplePath 、先に説明したグラフィックス・ドライバの例と同じフォーマットを使用します。

struct ExamplePath : public PlatformInterface::DrawingEngine::Path
{
    // S16.15 fixed point factor
    static const int32_t fixedPointFactor = (1 << 15);

    int16_t fillRule;

    ExamplePath(const PlatformInterface::PathData *pathData, PlatformInterface::PathFillRule fillRule);

    void free() { PlatformInterface::qul_delete(this); }

    const PlatformInterface::PathData *getPathData() { return path; }

    // Functions to access stored path/stroke elements
    int32_t *getFillPathData(void) { return fillElements.data(); }
    int32_t *getStrokePathData(void) { return strokeElements.data(); }

    // Functions to store fill elements in the platform optimized format
    inline void addElement(int32_t element) { fillElements.push_back(element); }
    inline void addElement(float element)
    {
        // Convert floating point values to fixed point
        fillElements.push_back(static_cast<int32_t>(element * fixedPointFactor));
    }

    // Functions to store stroke elements in the platform optimized format
    inline void addStrokeElement(int32_t element) { strokeElements.push_back(element); }
    inline void addStrokeElement(float element)
    {
        // Convert floating point values to fixed point
        strokeElements.push_back(static_cast<int32_t>(element * fixedPointFactor));
    }

    // Function to convert fill path data to platform optimized format
    void processFillPath();

    // Functions to return current element counts
    int32_t fillPathSize() { return fillElements.size() * sizeof(int32_t); }
    int32_t strokePathSize() { return strokeElements.size() * sizeof(int32_t); }

    // Functions to remove fill/stroke elements
    void clearStroke() { strokeElements.clear(); }
    void clearFill() { fillElements.clear(); }

private:
    // Status flag of the vector path preprocessing
    bool processingDone;

    // Path data in the common format
    const PlatformInterface::PathData *path;

    // Fill/stroke data format optimized for the platform
    std::vector<int32_t, Qul::PlatformInterface::Allocator<int32_t> > fillElements;
    std::vector<int32_t, Qul::PlatformInterface::Allocator<int32_t> > strokeElements;
};

fillRule 、structコンストラクタでプラットフォーム固有のフォーマットに変換できます。

ExamplePath::ExamplePath(const PlatformInterface::PathData *pathData, PlatformInterface::PathFillRule fillRule)
    : fillRule(fillRule == PlatformInterface::PathWindingFill ? HW_PATH_FILL_NON_ZERO : HW_PATH_FILL_FILL_EVEN_ODD)
    , processingDone(false)
    , path(pathData)
{}

次に、PlatformInterface::PathDataIterator でパス・データをトラバースし、プラットフォーム固有のフォーマットに変換する関数を作成する。

void ExamplePath::processFillPath()
{
    if (processingDone)
        return;

    PlatformInterface::PointF current(0.0, 0.0);

    PlatformInterface::PathDataIterator it(path);

    while (it.hasNext()) {
        PlatformInterface::PathDataSegment segment = it.next();
        switch (segment.type()) {
        case PlatformInterface::PathData::CloseSegment:
            addElement(static_cast<int32_t>(HW_PATH_CLOSE));
            break;
        case PlatformInterface::PathData::PathSeparatorSegment:
            break;
        case PlatformInterface::PathData::MoveSegment: {
            const PlatformInterface::PathDataMoveSegment *moveSegment
                = segment.as<PlatformInterface::PathDataMoveSegment>();
            addElement(static_cast<int32_t>(HW_PATH_MOVETO));
            addElement(moveSegment->target().x());
            addElement(moveSegment->target().y());
            current = moveSegment->target();
            break;
        }
        case PlatformInterface::PathData::LineSegment: {
            const PlatformInterface::PathDataLineSegment *lineSegment
                = segment.as<PlatformInterface::PathDataLineSegment>();
            addElement(static_cast<int32_t>(HW_PATH_LINETO));
            addElement(lineSegment->target().x());
            addElement(lineSegment->target().y());
            current = lineSegment->target();
            break;
        }
        case PlatformInterface::PathData::QuadraticBezierSegment: {
            const PlatformInterface::PathDataQuadraticBezierSegment *bezierSegment
                = segment.as<PlatformInterface::PathDataQuadraticBezierSegment>();
            // ...
            break;
        }
        case PlatformInterface::PathData::CubicBezierSegment: {
            const PlatformInterface::PathDataCubicBezierSegment *bezierSegment
                = segment.as<PlatformInterface::PathDataCubicBezierSegment>();
            // ...
            break;
        }
        case PlatformInterface::PathData::SmallCounterClockWiseArcSegment: {
            const PlatformInterface::PathDataSmallCounterClockWiseArcSegment *arcSegment
                = segment.as<PlatformInterface::PathDataSmallCounterClockWiseArcSegment>();
            // ...
            break;
        }
        case PlatformInterface::PathData::SmallClockWiseArcSegment: {
            const PlatformInterface::PathDataSmallClockWiseArcSegment *arcSegment
                = segment.as<PlatformInterface::PathDataSmallClockWiseArcSegment>();
            // ...
            break;
        }
        case PlatformInterface::PathData::LargeCounterClockWiseArcSegment: {
            const PlatformInterface::PathDataLargeCounterClockWiseArcSegment *arcSegment
                = segment.as<PlatformInterface::PathDataLargeCounterClockWiseArcSegment>();
            // ...
            break;
        }
        case PlatformInterface::PathData::LargeClockWiseArcSegment: {
            const PlatformInterface::PathDataLargeClockWiseArcSegment *arcSegment
                = segment.as<PlatformInterface::PathDataLargeClockWiseArcSegment>();
            // ...
            break;
        }
        default:
            QUL_ASSERT(false, QulError_PathData_UnknownSegmentType, segment.type());
            return;
        }
    }

    addElement(static_cast<int32_t>(HW_PATH_END));
    processingDone = true;
}

パスの割り当て

次に、PlatformInterface::DrawingEngine::allocatePath 関数を実装します。この関数は、パス・ハンドルが割り当てられたときに呼び出されます。

PlatformInterface::DrawingEngine::Path *ExampleDrawingEngine::allocatePath(const PlatformInterface::PathData *pathData,
                                                                           PlatformInterface::PathFillRule fillRule)
{
    return PlatformInterface::qul_new<Private::ExamplePath>(pathData, fillRule);
}

描画デバイスへのパスのブレンド

次にPlatformInterface::DrawingEngine::blendPath 関数を実装する。(これにはHardware accelerated blending で説明したようなカスタム描画エンジンを作成する必要があります)。この関数は、パスがtransformblendMode を使って描画デバイスにブレンドされようとするときに呼び出されます。その結果はclipRect でクリップされなければなりません。 は描画デバイス座標にあります。さらに、sourceOpacity は全体の不透明度を 0 から 256 の範囲で定義し、 256 は完全に不透明です。

  • fillBrushnullptr に設定 さ れていない と き は、 パスは、 パ ス を割 り 当て る 際に設定 さ れたPathFillRule に従っ て、 与えられたfillBrush で塗りつぶされます。
  • strokeBrushnullptr に設定 さ れていない と き は、 パ ス は、setStrokeProperties で設定 さ れたStrokeProperties に従っ て、 与えられたstrokeBrush に よ っ て描線 さ れます。
void ExampleDrawingEngine::blendPath(PlatformInterface::DrawingDevice *drawingDevice,
                                     PlatformInterface::DrawingEngine::Path *path,
                                     const PlatformInterface::Transform &transform,
                                     const PlatformInterface::Rect &clipRect,
                                     const PlatformInterface::Brush *fillBrush,
                                     const PlatformInterface::Brush *strokeBrush,
                                     int sourceOpacity,
                                     PlatformInterface::DrawingEngine::BlendMode blendMode)
{
    Private::ExamplePath *destinationPath = static_cast<Private::ExamplePath *>(path);

    float matrix[3][3];
    toHwMatrix3x3(transform, matrix);

    // HW_SetClip(clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());

    // HW_BlendMode_t HWblendMode =
    //     (blendMode == PlatformInterface::DrawingEngine::BlendMode_SourceOver ? HW_BLEND_SRC_OVER : HW_BLEND_NONE);

    if (fillBrush) {
        destinationPath->processFillPath();

        // HW_Path_t hw_path = {..., destinationPath->fillPathSize(), destinationPath->getFillPathData(), ...};

        if (fillBrush->pattern() == Qul::PlatformInterface::Brush::LinearGradientPattern) {
            const Qul::PlatformInterface::GradientStops &gradientStops = fillBrush->linearGradient().stops();
            // HW_GradientColorTable_Handle hw_gradientColorTable = nullptr;
            // hw_gradientColorTable = generateHW_GradientColorTable(gradientStops);

            // HW_DrawWithLinearGradient(..., &hw_path, destinationPath->fillRule, &matrix, HWblendMode, hw_gradientColorTable, hwLinearGradientParameters(fillBrush->linearGradient()), ...);

            // As an optimization it might make sense to keep a cache of HW gradient color tables
            // if (hw_gradientColorTable)
            //     freeHW_GradientColorTable(hw_gradientColorTable);
        } else if (fillBrush->pattern() == Qul::PlatformInterface::Brush::SolidPattern) {
            // HW_Draw(..., &hw_path, destinationPath->fillRule, &matrix, HWblendMode, fillBrush->color().value, ...);
        }
    }

    if (strokeBrush) {
        // HW_Path_t hw_path = {..., destinationPath->strokePathSize(), destinationPath->getStrokePathData(), ...};

        if (strokeBrush->pattern() == Qul::PlatformInterface::Brush::LinearGradientPattern) {
            const Qul::PlatformInterface::GradientStops &gradientStops = fillBrush->linearGradient().stops();
            // HW_GradientColorTable_Handle hw_gradientColorTable = nullptr;
            // hw_gradientColorTable = generateHW_GradientColorTable(gradientStops);

            // HW_DrawWithLinearGradient(..., &hw_path, destinationPath->fillRule, &matrix, HWblendMode, hw_gradientColorTable, hwLinearGradientParameters(strokeBrush->linearGradient()), ...);

            // As an optimization it might make sense to keep a cache of HW gradient color tables
            // if (hw_gradientColorTable)
            //     freeHW_GradientColorTable(hw_gradientColorTable);
        } else if (strokeBrush->pattern() == Qul::PlatformInterface::Brush::SolidPattern) {
            // HW_Draw(..., &hw_path, destinationPath->fillRule, &matrix, HWblendMode, strokeBrush->color().value, ...);
        }
    }

    // HW_SetClip(0, 0, screen->width(), screen->height());
}

パス・ストローク

最後に、Qul::PlatformInterface::DrawingEngine::setStrokeProperties 関数を実装します。LineCapStyleMiterLimit などのストローク・プロパティは、Qul::PlatformInterface::StrokeProperties からアクセスできます。

void ExampleDrawingEngine::setStrokeProperties(PlatformInterface::DrawingEngine::Path *path,
                                               const PlatformInterface::StrokeProperties &strokeProperties)
{
    Private::ExamplePath *destinationPath = static_cast<Private::ExamplePath *>(path);
    ...
}

ストロークのパスを生成する

Qul::PlatformInterface::PathDataStroker を使用すると、指定されたパスのストロークのアウトラインパスを作成できます。これは、プラットフォーム・ドライバ・インターフェースがストローク・プロパティをサポートしていない場合に便利です。PathDataStroker をサブクラス化し、以下の関数をオーバーライドすることで利用できます:

class ExamplePathDataStroker : public PlatformInterface::PathDataStroker
{
public:
    ExamplePathDataStroker(ExamplePath *data);

protected:
    void beginStroke() QUL_DECL_OVERRIDE;
    void endStroke() QUL_DECL_OVERRIDE;
    void lineTo(float x, float y) QUL_DECL_OVERRIDE;
    void moveTo(float x, float y) QUL_DECL_OVERRIDE;
    void cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, float ey) QUL_DECL_OVERRIDE;
    void arcTo(float x, float y, float rx, float ry, float rotation, bool largeArc, bool clockwise) QUL_DECL_OVERRIDE;

private:
    ExamplePath *destinationPath;
    PlatformInterface::PointF current;
};

各関数のサポー ト を実装 し て、 プ ラ ッ ト フ ォーム用に最適化 さ れた形式でパ ス デー タ を流 し 込みます。これは、ストロークを表すパスを生成し、それを塗りつぶします。

ExamplePathDataStroker::ExamplePathDataStroker(ExamplePath *data)
    : PathDataStroker(data->getPathData())
    , destinationPath(data)
{}

void ExamplePathDataStroker::beginStroke()
{
    destinationPath->clearStroke();
}

void ExamplePathDataStroker::endStroke()
{
    destinationPath->addStrokeElement(static_cast<int32_t>(HW_PATH_END));
}

void ExamplePathDataStroker::lineTo(float x, float y)
{
    destinationPath->addStrokeElement(static_cast<int32_t>(HW_PATH_LINETO));
    destinationPath->addStrokeElement(x);
    destinationPath->addStrokeElement(y);
    current.setX(x);
    current.setY(y);
}

void ExamplePathDataStroker::moveTo(float x, float y)
{
    destinationPath->addStrokeElement(static_cast<int32_t>(HW_PATH_MOVETO));
    destinationPath->addStrokeElement(x);
    destinationPath->addStrokeElement(y);
    current.setX(x);
    current.setY(y);
}

void ExamplePathDataStroker::cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, float ey)
{
    // ...
    current.setX(ex);
    current.setY(ey);
}

void ExamplePathDataStroker::arcTo(float x, float y, float rx, float ry, float rotation, bool largeArc, bool clockwise)
{
    // ...
    current.setX(x);
    current.setY(y);
}

ストロークのパスはQul::PlatformInterface::DrawingEngine::setStrokeProperties 関数で事前計算できます。

void ExampleDrawingEngine::setStrokeProperties(PlatformInterface::DrawingEngine::Path *path,
                                               const PlatformInterface::StrokeProperties &strokeProperties)
{
    Private::ExamplePath *destinationPath = static_cast<Private::ExamplePath *>(path);

    Private::ExamplePathDataStroker stroker(destinationPath);
    stroker.setStrokeProperties(strokeProperties);
    stroker.stroke();
}

特定の Qt ライセンスの下で利用可能です。
詳細はこちら。