summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Romain Guy <romainguy@google.com> 2012-02-28 18:17:02 -0800
committer Romain Guy <romainguy@google.com> 2012-02-29 19:14:37 -0800
commit9777173eb6c9eb97c7921c8288ebc65e3ab3ce6f (patch)
tree0c2749e602c75cf72a3a2d6ffd480a47f975fdd5
parent765dcf32307dbd93ce43f064c426ce157be2d2ae (diff)
Full implementation of Canvas.drawPath()
Change-Id: I23223b89770a0cd2b4762365bead9bfddb094290
-rw-r--r--libs/hwui/FontRenderer.cpp164
-rw-r--r--libs/hwui/FontRenderer.h20
-rw-r--r--libs/hwui/OpenGLRenderer.cpp60
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java55
4 files changed, 238 insertions, 61 deletions
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 3df105be19ea..f2bb6ec7f723 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -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,19 +298,13 @@ 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) {
+ uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
if (numGlyphs == 0 || text == NULL || len == 0) {
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
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index b767be57fbf8..4fc5862b1ee5 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -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() { };
@@ -305,7 +315,7 @@ protected:
void allocateTextureMemory(CacheTexture* cacheTexture);
void deallocateTextureMemory(CacheTexture* cacheTexture);
void initTextTexture();
- CacheTexture *createCacheTexture(int width, int height, bool allocate);
+ CacheTexture* createCacheTexture(int width, int height, bool allocate);
void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
uint32_t *retOriginX, uint32_t *retOriginY);
@@ -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;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index ebb6d88a0f77..73625b721b82 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -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) {
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java
index b798ee702dfc..9849e3c61ba8 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java
@@ -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();
}
}