Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9777173e authored by Romain Guy's avatar Romain Guy
Browse files

Full implementation of Canvas.drawPath()

Change-Id: I23223b89770a0cd2b4762365bead9bfddb094290
parent 765dcf32
Loading
Loading
Loading
Loading
+145 −19
Original line number Diff line number Diff line
@@ -39,6 +39,8 @@ namespace uirenderer {
#define MAX_TEXT_CACHE_WIDTH 2048
#define TEXTURE_BORDER_SIZE 2

#define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16)

///////////////////////////////////////////////////////////////////////////////
// CacheTextureLine
///////////////////////////////////////////////////////////////////////////////
@@ -163,6 +165,44 @@ void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
    }
}

void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
        SkPathMeasure& measure, SkPoint* position, SkVector* tangent) {
    const float halfWidth = glyph->mBitmapWidth * 0.5f;
    const float height = glyph->mBitmapHeight;

    float nPenX = glyph->mBitmapLeft;
    vOffset += glyph->mBitmapTop + height;

    const float u1 = glyph->mBitmapMinU;
    const float u2 = glyph->mBitmapMaxU;
    const float v1 = glyph->mBitmapMinV;
    const float v2 = glyph->mBitmapMaxV;

    SkPoint destination[4];
    measure.getPosTan(x + hOffset + nPenX + halfWidth, position, tangent);

    // Move along the tangent and offset by the normal
    destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset,
            -tangent->fY * halfWidth + tangent->fX * vOffset);
    destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset,
            tangent->fY * halfWidth + tangent->fX * vOffset);
    destination[2].set(destination[1].fX + tangent->fY * height,
            destination[1].fY - tangent->fX * height);
    destination[3].set(destination[0].fX + tangent->fY * height,
            destination[0].fY - tangent->fX * height);

    mState->appendRotatedMeshQuad(
            position->fX + destination[0].fX,
            position->fY + destination[0].fY, u1, v2,
            position->fX + destination[1].fX,
            position->fY + destination[1].fY, u2, v2,
            position->fX + destination[2].fX,
            position->fY + destination[2].fY, u2, v1,
            position->fX + destination[3].fX,
            position->fY + destination[3].fY, u1, v1,
            glyph->mCachedTextureLine->mCacheTexture);
}

CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) {
    CachedGlyphInfo* cachedGlyph = NULL;
    ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
@@ -198,6 +238,56 @@ void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len
            0, 0, NULL, positions);
}

void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
        int numGlyphs, SkPath* path, float hOffset, float vOffset) {
    if (numGlyphs == 0 || text == NULL || len == 0) {
        return;
    }

    text += start;

    int glyphsCount = 0;
    SkFixed prevRsbDelta = 0;

    float penX = 0.0f;

    SkPoint position;
    SkVector tangent;

    SkPathMeasure measure(*path, false);
    float pathLength = SkScalarToFloat(measure.getLength());

    if (paint->getTextAlign() != SkPaint::kLeft_Align) {
        float textWidth = SkScalarToFloat(paint->measureText(text, len));
        float pathOffset = pathLength;
        if (paint->getTextAlign() == SkPaint::kCenter_Align) {
            textWidth *= 0.5f;
            pathOffset *= 0.5f;
        }
        penX += pathOffset - textWidth;
    }

    while (glyphsCount < numGlyphs && penX <= pathLength) {
        glyph_t glyph = GET_GLYPH(text);

        if (IS_END_OF_STRING(glyph)) {
            break;
        }

        CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
        penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
        prevRsbDelta = cachedGlyph->mRsbDelta;

        if (cachedGlyph->mIsValid) {
            drawCachedGlyph(cachedGlyph, roundf(penX), hOffset, vOffset, measure, &position, &tangent);
        }

        penX += SkFixedToFloat(cachedGlyph->mAdvanceX);

        glyphsCount++;
    }
}

void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
        int numGlyphs, Rect *bounds) {
    if (bounds == NULL) {
@@ -208,8 +298,6 @@ void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t le
    render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, NULL);
}

#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)

void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
        int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
        uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
@@ -217,10 +305,6 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len
        return;
    }

    int glyphsCount = 0;

    text += start;

    static RenderGlyph gRenderGlyph[] = {
            &android::uirenderer::Font::drawCachedGlyph,
            &android::uirenderer::Font::drawCachedGlyphBitmap,
@@ -228,14 +312,15 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len
    };
    RenderGlyph render = gRenderGlyph[mode];

    text += start;
    int glyphsCount = 0;

    if (CC_LIKELY(positions == NULL)) {
        SkFixed prevRsbDelta = 0;

        float penX = x;
        float penX = x + 0.5f;
        int penY = y;

        penX += 0.5f;

        while (glyphsCount < numGlyphs) {
            glyph_t glyph = GET_GLYPH(text);

@@ -245,7 +330,7 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len
            }

            CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
            penX += SkFixedToFloat(SkAutoKern_AdjustF(prevRsbDelta, cachedGlyph->mLsbDelta));
            penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
            prevRsbDelta = cachedGlyph->mRsbDelta;

            // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
@@ -582,6 +667,7 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp
            cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
        }
    }

    cachedGlyph->mIsValid = true;
}

@@ -758,15 +844,9 @@ void FontRenderer::issueDrawCommand() {
    mDrawn = true;
}

void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
        float x2, float y2, float u2, float v2,
        float x3, float y3, float u3, float v3,
void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
        float x4, float y4, float u4, float v4, CacheTexture* texture) {

    if (mClip &&
            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
        return;
    }
    if (texture != mCurrentCacheTexture) {
        if (mCurrentQuadIndex != 0) {
            // First, draw everything stored already which uses the previous texture
@@ -802,6 +882,18 @@ void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
    (*currentPos++) = v4;

    mCurrentQuadIndex++;
}

void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
        float x4, float y4, float u4, float v4, CacheTexture* texture) {

    if (mClip &&
            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
        return;
    }

    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);

    if (mBounds) {
        mBounds->left = fmin(mBounds->left, x1);
@@ -816,6 +908,25 @@ void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
    }
}

void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
        float x4, float y4, float u4, float v4, CacheTexture* texture) {

    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);

    if (mBounds) {
        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
    }

    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
        issueDrawCommand();
        mCurrentQuadIndex = 0;
    }
}

uint32_t FontRenderer::getRemainingCacheCapacity() {
    uint32_t remainingCapacity = 0;
    float totalPixels = 0;
@@ -956,6 +1067,21 @@ bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *t
    return mDrawn;
}

bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
        float hOffset, float vOffset, Rect* bounds) {
    if (!mCurrentFont) {
        ALOGE("No font set");
        return false;
    }

    initRender(clip, bounds);
    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
    finishRender();

    return mDrawn;
}

void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
    // Compute gaussian weights for the blur
    // e is the euler's number
+19 −1
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@

#include <SkScalerContext.h>
#include <SkPaint.h>
#include <SkPathMeasure.h>
#include <SkPoint.h>

#include <GLES2/gl2.h>

@@ -155,6 +157,9 @@ public:
    void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
            int numGlyphs, int x, int y, const float* positions);

    void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
            int numGlyphs, SkPath* path, float hOffset, float vOffset);

    /**
     * Creates a new font associated with the specified font state.
     */
@@ -200,6 +205,8 @@ protected:
    void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
            uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
            Rect* bounds, const float* pos);
    void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
            SkPathMeasure& measure, SkPoint* position, SkVector* tangent);

    CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit);

@@ -244,6 +251,9 @@ public:
    // bounds is an out parameter
    bool renderPosText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
            uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds);
    // bounds is an out parameter
    bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
            uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds);

    struct DropShadow {
        DropShadow() { };
@@ -320,10 +330,18 @@ protected:
    void precacheLatin(SkPaint* paint);

    void issueDrawCommand();
    void appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
            float x2, float y2, float u2, float v2,
            float x3, float y3, float u3, float v3,
            float x4, float y4, float u4, float v4, CacheTexture* texture);
    void appendMeshQuad(float x1, float y1, float u1, float v1,
            float x2, float y2, float u2, float v2,
            float x3, float y3, float u3, float v3,
            float x4, float y4, float u4, float v4, CacheTexture* texture);
    void appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
            float x2, float y2, float u2, float v2,
            float x3, float y3, float u3, float v3,
            float x4, float y4, float u4, float v4, CacheTexture* texture);

    uint32_t mSmallCacheWidth;
    uint32_t mSmallCacheHeight;
+20 −40
Original line number Diff line number Diff line
@@ -2300,15 +2300,6 @@ void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count,
        return;
    }

    float x = 0.0f;
    float y = 0.0f;

    const bool pureTranslate = mSnapshot->transform->isPureTranslate();
    if (CC_LIKELY(pureTranslate)) {
        x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
        y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
    }

    FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(paint);
    fontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()),
            paint->getTextSize());
@@ -2326,41 +2317,30 @@ void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count,
    setupDrawShader();
    setupDrawBlending(true, mode);
    setupDrawProgram();
    setupDrawModelView(x, y, x, y, pureTranslate, true);
    setupDrawModelView(0.0f, 0.0f, 0.0f, 0.0f, false, true);
    setupDrawTexture(fontRenderer.getTexture(true));
    setupDrawPureColorUniforms();
    setupDrawColorFilterUniforms();
    setupDrawShaderUniforms(pureTranslate);
    setupDrawShaderUniforms(false);

    const Rect* clip = &mSnapshot->getLocalClip();
    Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);

//    mat4 pathTransform;
//    pathTransform.loadTranslate(hOffset, vOffset, 0.0f);
//
//    float offset = 0.0f;
//    SkPathMeasure pathMeasure(*path, false);
//
//    if (paint->getTextAlign() != SkPaint::kLeft_Align) {
//        SkScalar pathLength = pathMeasure.getLength();
//        if (paint->getTextAlign() == SkPaint::kCenter_Align) {
//            pathLength = SkScalarHalf(pathLength);
//        }
//        offset += SkScalarToFloat(pathLength);
//    }

//        SkScalar x;
//        SkPath      tmp;
//        SkMatrix    m(scaledMatrix);
//
//        m.postTranslate(xpos + hOffset, 0);
//        if (matrix) {
//            m.postConcat(*matrix);
//        }
//        morphpath(&tmp, *iterPath, meas, m);
//        if (fDevice) {
//            fDevice->drawPath(*this, tmp, iter.getPaint(), NULL, true);
//        } else {
//            this->drawPath(tmp, iter.getPaint(), NULL, true);
//        }
//    }
#if RENDER_LAYERS_AS_REGIONS
    const bool hasActiveLayer = hasLayer();
#else
    const bool hasActiveLayer = false;
#endif

    if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
            hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) {
#if RENDER_LAYERS_AS_REGIONS
        if (hasActiveLayer) {
            mSnapshot->transform->mapRect(bounds);
            dirtyLayerUnchecked(bounds, getRegion());
        }
#endif
    }
}

void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
+54 −1
Original line number Diff line number Diff line
@@ -21,18 +21,21 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.os.Bundle;
import android.view.View;

@SuppressWarnings({"UnusedDeclaration"})
public class TextOnPathActivity extends Activity {
    private Path mPath;
    private Path mStraightPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mPath = makePath();
        mStraightPath = makeStraightPath();

        final TextOnPathView view = new TextOnPathView(this);
        setContentView(view);
@@ -51,11 +54,28 @@ public class TextOnPathActivity extends Activity {
        path.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
    }

    private Path makeStraightPath() {
        Path path = new Path();
        buildStraightPath(path);
        return path;
    }

    private void buildStraightPath(Path path) {
        path.moveTo(0.0f, 0.0f);
        path.lineTo(400.0f, 0.0f);
    }

    public class TextOnPathView extends View {
        private static final String TEST_STRING = "Hello OpenGL renderer, text on path! ";

        private final Paint mPaint;
        private final Paint mPathPaint;
        private final String mText;
        private final PathMeasure mMeasure;
        private final float mLength;
        private final float[] mLines;
        private final float[] mPos;
        private final float[] mTan;

        public TextOnPathView(Context c) {
            super(c);
@@ -64,11 +84,23 @@ public class TextOnPathActivity extends Activity {
            mPaint.setAntiAlias(true);
            mPaint.setColor(0xff000000);

            mPathPaint = new Paint();
            mPathPaint.setAntiAlias(true);
            mPathPaint.setStyle(Paint.Style.STROKE);
            mPathPaint.setColor(0xff000099);

            StringBuilder builder = new StringBuilder(TEST_STRING.length() * 2);
            for (int i = 0; i < 2; i++) {
                builder.append(TEST_STRING);
            }
            mText = builder.toString();

            mMeasure = new PathMeasure(mPath, false);
            mLength = mMeasure.getLength();
            
            mLines = new float[100 * 4];
            mPos = new float[2];
            mTan = new float[2];
        }

        @Override
@@ -81,19 +113,40 @@ public class TextOnPathActivity extends Activity {
            canvas.translate(400.0f, 350.0f);
            mPaint.setTextAlign(Paint.Align.LEFT);
            canvas.drawTextOnPath(mText + mText, mPath, 0.0f, 0.0f, mPaint);
            canvas.drawPath(mPath, mPathPaint);
            
            for (int i = 0; i < 100; i++) {
                mMeasure.getPosTan(i * mLength / 100.0f, mPos, mTan);
                mLines[i * 4    ] = mPos[0];
                mLines[i * 4 + 1] = mPos[1];
                mLines[i * 4 + 2] = mPos[0] + mTan[1] * 15;
                mLines[i * 4 + 3] = mPos[1] - mTan[0] * 15;
            }
            canvas.drawLines(mLines, mPathPaint);
            
            canvas.translate(200.0f, 0.0f);
            canvas.drawTextOnPath(mText + mText, mStraightPath, 0.0f, 0.0f, mPaint);
            canvas.drawPath(mStraightPath, mPathPaint);

            canvas.restore();

            canvas.save();
            canvas.translate(150.0f, 60.0f);
            canvas.drawTextOnPath(mText, mPath, 0.0f, 0.0f, mPaint);
            canvas.drawTextOnPath(mText, mPath, 0.0f, 10.0f, mPaint);
            mMeasure.getPosTan(5.0f, mPos, mTan);
            canvas.drawLine(mPos[0], mPos[1], mPos[0] + mTan[1] * 10, mPos[1] - mTan[0] * 10,
                    mPathPaint);
            canvas.drawPath(mPath, mPathPaint);

            canvas.translate(250.0f, 0.0f);
            mPaint.setTextAlign(Paint.Align.CENTER);
            canvas.drawTextOnPath(mText, mPath, 0.0f, 0.0f, mPaint);
            canvas.drawPath(mPath, mPathPaint);

            canvas.translate(250.0f, 0.0f);
            mPaint.setTextAlign(Paint.Align.RIGHT);
            canvas.drawTextOnPath(mText, mPath, 0.0f, 0.0f, mPaint);
            canvas.drawPath(mPath, mPathPaint);
            canvas.restore();
        }
    }