This change does not apply to drawPosText() and drawTextOnPath() yet.
Prior to this change, glyphs were always rasterized based on the
font size specified in the paint. All transforms were then applied
on the resulting texture. This creates rather ugly results when
text is scaled and/or rotated.
With this change, the font renderer will apply the current transform
matrix to the glyph before they are rasterized. This generates much
better looking results.
Change-Id: I0141b6ff18db35e1213e7a3ab9db1ecaf03d7a9c
// Matrix
///////////////////////////////////////////////////////////////////////////////
+const Matrix4& Matrix4::identity() {
+ static Matrix4 sIdentity;
+ return sIdentity;
+}
+
void Matrix4::loadIdentity() {
data[kScaleX] = 1.0f;
data[kSkewY] = 0.0f;
void dump() const;
+ static const Matrix4& identity();
+
private:
mutable uint32_t mType;
mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
} else {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, mIdentity);
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom);
}
}
void OpenGLRenderer::setupDrawModelViewIdentity(bool offset) {
- mCaches.currentProgram->set(mOrthoMatrix, mIdentity, *mSnapshot->transform, offset);
+ mCaches.currentProgram->set(mOrthoMatrix, mat4::identity(), *mSnapshot->transform, offset);
}
void OpenGLRenderer::setupDrawModelView(float left, float top, float right, float bottom,
dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
}
} else {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, mIdentity);
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
if (mTrackDirtyRegions && dirty) dirtyLayer(left, top, right, bottom);
}
}
void OpenGLRenderer::setupDrawShaderIdentityUniforms() {
if (mDrawModifiers.mShader) {
mDrawModifiers.mShader->setupProgram(mCaches.currentProgram,
- mIdentity, *mSnapshot, &mTextureUnit);
+ mat4::identity(), *mSnapshot, &mTextureUnit);
}
}
}
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, *mSnapshot->transform);
+ fontRenderer.setFont(paint, mat4::identity());
int alpha;
SkXfermode::Mode mode;
return DrawGlInfo::kStatusDone;
}
-#if DEBUG_GLYPHS
- ALOGD("OpenGLRenderer drawText() with FontID=%d",
- SkTypeface::UniqueID(paint->getTypeface()));
-#endif
-
- FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, *mSnapshot->transform);
-
const float oldX = x;
const float oldY = y;
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);
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
+ FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
+
if (CC_UNLIKELY(mDrawModifiers.mHasShadow)) {
- drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, alpha, mode,
- oldX, oldY);
+ fontRenderer.setFont(paint, mat4::identity());
+ drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
+ alpha, mode, oldX, oldY);
}
+ fontRenderer.setFont(paint, pureTranslate ? mat4::identity() : *mSnapshot->transform);
+
// Pick the appropriate texture filtering
- bool linearFilter = mSnapshot->transform->changesBounds();
- if (pureTranslate && !linearFilter) {
- linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
- }
+ bool linearFilter = !mSnapshot->transform->isPureTranslate() ||
+ fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
// The font renderer will always use texture unit 0
mCaches.activeTexture(0);
setupDrawShader();
setupDrawBlending(true, mode);
setupDrawProgram();
- setupDrawModelView(x, y, x, y, pureTranslate, true);
+ setupDrawModelView(x, y, x, y, true, true);
// See comment above; the font renderer must use texture unit 0
// assert(mTextureUnit == 0)
setupDrawTexture(fontRenderer.getTexture(linearFilter));
setupDrawPureColorUniforms();
setupDrawColorFilterUniforms();
- setupDrawShaderUniforms(pureTranslate);
+ setupDrawShaderUniforms(true);
setupDrawTextGammaUniforms();
- const Rect* clip = pureTranslate ? mSnapshot->clipRect :
- (mSnapshot->hasPerspectiveTransform() ? NULL : &mSnapshot->getLocalClip());
+ const Rect* clip = mSnapshot->hasPerspectiveTransform() ? NULL : mSnapshot->clipRect;
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
const bool hasActiveLayer = hasLayer();
}
if (status && hasActiveLayer) {
- if (!pureTranslate) {
- mSnapshot->transform->mapRect(bounds);
- }
dirtyLayerUnchecked(bounds, getRegion());
}
}
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, *mSnapshot->transform);
+ fontRenderer.setFont(paint, mat4::identity());
int alpha;
SkXfermode::Mode mode;
// List of layers to update at the beginning of a frame
Vector<Layer*> mLayerUpdates;
- // Indentity matrix
- const mat4 mIdentity;
-
// Indicates whether the clip must be restored
bool mDirtyClip;
mStyle = paint->getStyle();
mStrokeWidth = paint->getStrokeWidth();
mAntiAliasing = paint->isAntiAlias();
+ mLookupTransform[SkMatrix::kMScaleX] = matrix.data[mat4::kScaleX];
+ mLookupTransform[SkMatrix::kMScaleY] = matrix.data[mat4::kScaleY];
+ mLookupTransform[SkMatrix::kMSkewX] = matrix.data[mat4::kSkewX];
+ mLookupTransform[SkMatrix::kMSkewY] = matrix.data[mat4::kSkewY];
}
Font::~Font() {
hash = JenkinsHashMix(hash, android::hash_type(mStyle));
hash = JenkinsHashMix(hash, android::hash_type(mStrokeWidth));
hash = JenkinsHashMix(hash, int(mAntiAliasing));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleX]));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleY]));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMSkewX]));
+ hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMSkewY]));
return JenkinsHashWhiten(hash);
}
deltaInt = int(lhs.mAntiAliasing) - int(rhs.mAntiAliasing);
if (deltaInt != 0) return deltaInt;
+ if (lhs.mLookupTransform[SkMatrix::kMScaleX] <
+ rhs.mLookupTransform[SkMatrix::kMScaleX]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMScaleX] >
+ rhs.mLookupTransform[SkMatrix::kMScaleX]) return +1;
+
+ if (lhs.mLookupTransform[SkMatrix::kMScaleY] <
+ rhs.mLookupTransform[SkMatrix::kMScaleY]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMScaleY] >
+ rhs.mLookupTransform[SkMatrix::kMScaleY]) return +1;
+
+ if (lhs.mLookupTransform[SkMatrix::kMSkewX] <
+ rhs.mLookupTransform[SkMatrix::kMSkewX]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMSkewX] >
+ rhs.mLookupTransform[SkMatrix::kMSkewX]) return +1;
+
+ if (lhs.mLookupTransform[SkMatrix::kMSkewY] <
+ rhs.mLookupTransform[SkMatrix::kMSkewY]) return -1;
+ if (lhs.mLookupTransform[SkMatrix::kMSkewY] >
+ rhs.mLookupTransform[SkMatrix::kMSkewY]) return +1;
+
return 0;
}
int32_t bX = 0, bY = 0;
for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
-#if DEBUG_FONT_RENDERER
- if (bX < 0 || bY < 0 || bX >= (int32_t) bitmapW || bY >= (int32_t) bitmapH) {
- ALOGE("Skipping invalid index");
- continue;
- }
-#endif
uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
bitmap[bY * bitmapW + bX] = tempCol;
}
ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
if (index >= 0) {
cachedGlyph = mCachedGlyphs.valueAt(index);
+
+ // Is the glyph still in texture cache?
+ if (!cachedGlyph->mIsValid) {
+ const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit, &mDescription.mLookupTransform);
+ updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching);
+ }
} else {
cachedGlyph = cacheGlyph(paint, textUnit, precaching);
}
- // Is the glyph still in texture cache?
- if (!cachedGlyph->mIsValid) {
- const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit, NULL);
- updateGlyphCache(paint, skiaGlyph, cachedGlyph, precaching);
- }
-
return cachedGlyph;
}
// If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
if (cachedGlyph->mIsValid) {
- int penX = x + positions[(glyphsCount << 1)];
- int penY = y + positions[(glyphsCount << 1) + 1];
-
- switch (align) {
- case SkPaint::kRight_Align:
- penX -= SkFixedToFloat(cachedGlyph->mAdvanceX);
- penY -= SkFixedToFloat(cachedGlyph->mAdvanceY);
- break;
- case SkPaint::kCenter_Align:
- penX -= SkFixedToFloat(cachedGlyph->mAdvanceX >> 1);
- penY -= SkFixedToFloat(cachedGlyph->mAdvanceY >> 1);
- default:
- break;
+ float penX = x + positions[(glyphsCount << 1)];
+ float penY = y + positions[(glyphsCount << 1) + 1];
+
+ if (!mTransform.isIdentity()) {
+ mTransform.mapPoint(penX, penY);
}
- (*this.*render)(cachedGlyph, penX, penY,
+ (*this.*render)(cachedGlyph, roundf(penX), roundf(penY),
bitmap, bitmapW, bitmapH, bounds, positions);
}
// Get the bitmap for the glyph
if (!skiaGlyph.fImage) {
- paint->findImage(skiaGlyph, NULL);
+ paint->findImage(skiaGlyph, &mDescription.mLookupTransform);
}
mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching);
CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
- const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, NULL);
+ const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform);
newGlyph->mGlyphIndex = skiaGlyph.fID;
newGlyph->mIsValid = false;
Font* font = state->mActiveFonts.get(description);
if (font) {
+ font->mTransform.load(matrix);
return font;
}
uint8_t mStyle;
float mStrokeWidth;
bool mAntiAliasing;
+ SkMatrix mLookupTransform;
};
~Font();
// Cache of glyphs
DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
+
+ mat4 mTransform;
};
inline int strictly_order_type(const Font::FontDescription& lhs,
<activity
android:name="ScaledTextActivity"
- android:label="_ScaledText">
+ android:label="_ScaledText"
+ android:theme="@android:style/Theme.Holo.Light">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
public ScaledTextView(Context c) {
super(c);
+ setLayerType(LAYER_TYPE_HARDWARE, null);
mPath = makePath();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- canvas.drawARGB(255, 255, 255, 255);
canvas.drawText(TEXT, 30.0f, 30.0f, mPaint);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ canvas.drawText(TEXT, 30.0f, 50.0f, mPaint);
+ mPaint.setTextAlign(Paint.Align.RIGHT);
+ canvas.drawText(TEXT, 30.0f, 70.0f, mPaint);
- canvas.translate(0.0f, 50.0f);
+ canvas.save();
+ canvas.translate(400.0f, 0.0f);
+ canvas.scale(3.0f, 3.0f);
+ mPaint.setTextAlign(Paint.Align.LEFT);
+ mPaint.setStrikeThruText(true);
+ canvas.drawText(TEXT, 30.0f, 30.0f, mPaint);
+ mPaint.setStrikeThruText(false);
+ mPaint.setTextAlign(Paint.Align.CENTER);
+ canvas.drawText(TEXT, 30.0f, 50.0f, mPaint);
+ mPaint.setTextAlign(Paint.Align.RIGHT);
+ canvas.drawText(TEXT, 30.0f, 70.0f, mPaint);
+ canvas.restore();
+
+ mPaint.setTextAlign(Paint.Align.LEFT);
+ canvas.translate(0.0f, 100.0f);
canvas.save();
canvas.scale(mScale, mScale);