Add support for paths.
Rendering is implementing by rasterizing the paths into A8 textures.
This cna be extremely inefficient if the path changes often.
Change-Id: I609343f304ae38e0d319359403ee73b9b5b3c93a
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 63a0c4c..8e1338d 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -350,7 +350,6 @@
@Override
public void setDrawFilter(DrawFilter filter) {
- // TODO: Implement PaintDrawFilter
mFilter = filter;
}
@@ -522,9 +521,13 @@
@Override
public void drawPath(Path path, Paint paint) {
- throw new UnsupportedOperationException();
+ boolean hasModifier = setupModifiers(paint);
+ nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
+ if (hasModifier) nResetModifiers(mRenderer);
}
+ private native void nDrawPath(int renderer, int path, int paint);
+
@Override
public void drawPicture(Picture picture) {
throw new UnsupportedOperationException();
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 142e194..9fd2b86 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -228,6 +228,11 @@
renderer->drawRect(left, top, right, bottom, paint);
}
+static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject canvas,
+ OpenGLRenderer* renderer, SkPath* path, SkPaint* paint) {
+ renderer->drawPath(path, paint);
+}
+
// ----------------------------------------------------------------------------
// Shaders and color filters
// ----------------------------------------------------------------------------
@@ -317,6 +322,7 @@
{ "nDrawPatch", "(II[BFFFFI)V", (void*) android_view_GLES20Canvas_drawPatch },
{ "nDrawColor", "(III)V", (void*) android_view_GLES20Canvas_drawColor },
{ "nDrawRect", "(IFFFFI)V", (void*) android_view_GLES20Canvas_drawRect },
+ { "nDrawPath", "(III)V", (void*) android_view_GLES20Canvas_drawPath },
{ "nResetModifiers", "(I)V", (void*) android_view_GLES20Canvas_resetModifiers },
{ "nSetupShader", "(II)V", (void*) android_view_GLES20Canvas_setupShader },
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 281823a..cb2c6a2 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -593,5 +593,8 @@
private static native void native_transform(int nPath, int matrix);
private static native void finalizer(int nPath);
- private final int mNativePath;
+ /**
+ * @hide
+ */
+ public final int mNativePath;
}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8f28612..0444964 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -9,6 +9,7 @@
OpenGLRenderer.cpp \
Patch.cpp \
PatchCache.cpp \
+ PathCache.cpp \
Program.cpp \
ProgramCache.cpp \
SkiaColorFilter.cpp \
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index d694039..a72045b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -37,7 +37,8 @@
///////////////////////////////////////////////////////////////////////////////
#define DEFAULT_TEXTURE_CACHE_SIZE 20.0f
-#define DEFAULT_LAYER_CACHE_SIZE 10.0f
+#define DEFAULT_LAYER_CACHE_SIZE 6.0f
+#define DEFAULT_PATH_CACHE_SIZE 6.0f
#define DEFAULT_PATCH_CACHE_SIZE 100
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
@@ -105,6 +106,7 @@
mTextureCache(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)),
mGradientCache(MB(DEFAULT_GRADIENT_CACHE_SIZE)),
+ mPathCache(MB(DEFAULT_PATH_CACHE_SIZE)),
mPatchCache(DEFAULT_PATCH_CACHE_SIZE) {
LOGD("Create OpenGLRenderer");
@@ -125,11 +127,18 @@
if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, NULL) > 0) {
LOGD(" Setting gradient cache size to %sMB", property);
- mLayerCache.setMaxSize(MB(atof(property)));
+ mGradientCache.setMaxSize(MB(atof(property)));
} else {
LOGD(" Using default gradient cache size of %.2fMB", DEFAULT_GRADIENT_CACHE_SIZE);
}
+ if (property_get(PROPERTY_PATH_CACHE_SIZE, property, NULL) > 0) {
+ LOGD(" Setting path cache size to %sMB", property);
+ mPathCache.setMaxSize(MB(atof(property)));
+ } else {
+ LOGD(" Using default path cache size of %.2fMB", DEFAULT_PATH_CACHE_SIZE);
+ }
+
mCurrentProgram = NULL;
mShader = NULL;
mColorFilter = NULL;
@@ -597,6 +606,73 @@
glDisableVertexAttribArray(texCoordsSlot);
}
+void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
+ GLuint textureUnit = 0;
+ glActiveTexture(gTextureUnits[textureUnit]);
+
+ PathTexture* texture = mPathCache.get(path, paint);
+
+ int alpha;
+ SkXfermode::Mode mode;
+ getAlphaAndMode(paint, &alpha, &mode);
+
+ uint32_t color = paint->getColor();
+ const GLfloat a = alpha / 255.0f;
+ const GLfloat r = a * ((color >> 16) & 0xFF) / 255.0f;
+ const GLfloat g = a * ((color >> 8) & 0xFF) / 255.0f;
+ const GLfloat b = a * ((color ) & 0xFF) / 255.0f;
+
+ // Describe the required shaders
+ ProgramDescription description;
+ description.hasTexture = true;
+ description.hasAlpha8Texture = true;
+ if (mShader) {
+ mShader->describe(description, mExtensions);
+ }
+ if (mColorFilter) {
+ mColorFilter->describe(description, mExtensions);
+ }
+
+ // Build and use the appropriate shader
+ useProgram(mProgramCache.get(description));
+
+ // Setup the blending mode
+ chooseBlending(true, mode);
+ bindTexture(texture->id, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit);
+ glUniform1i(mCurrentProgram->getUniform("sampler"), textureUnit);
+
+ int texCoordsSlot = mCurrentProgram->getAttrib("texCoords");
+ glEnableVertexAttribArray(texCoordsSlot);
+
+ // Setup attributes
+ glVertexAttribPointer(mCurrentProgram->position, 2, GL_FLOAT, GL_FALSE,
+ gMeshStride, &mMeshVertices[0].position[0]);
+ glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE,
+ gMeshStride, &mMeshVertices[0].texture[0]);
+
+ // Setup uniforms
+ mModelView.loadTranslate(texture->left - texture->offset,
+ texture->top - texture->offset, 0.0f);
+ mModelView.scale(texture->width, texture->height, 1.0f);
+ mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
+
+ glUniform4f(mCurrentProgram->color, r, g, b, a);
+
+ textureUnit++;
+ // Setup attributes and uniforms required by the shaders
+ if (mShader) {
+ mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit);
+ }
+ if (mColorFilter) {
+ mColorFilter->setupProgram(mCurrentProgram);
+ }
+
+ // Draw the mesh
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+
+ glDisableVertexAttribArray(texCoordsSlot);
+}
+
///////////////////////////////////////////////////////////////////////////////
// Shaders
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index d2a291f..76783e9 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -44,6 +44,7 @@
#include "ProgramCache.h"
#include "SkiaShader.h"
#include "SkiaColorFilter.h"
+#include "PathCache.h"
namespace android {
namespace uirenderer {
@@ -92,6 +93,7 @@
float right, float bottom, const SkPaint* paint);
void drawColor(int color, SkXfermode::Mode mode);
void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
+ void drawPath(SkPath* path, SkPaint* paint);
void resetShader();
void setupShader(SkiaShader* shader);
@@ -307,6 +309,7 @@
LayerCache mLayerCache;
GradientCache mGradientCache;
ProgramCache mProgramCache;
+ PathCache mPathCache;
PatchCache mPatchCache;
}; // class OpenGLRenderer
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
new file mode 100644
index 0000000..67a5f92
--- /dev/null
+++ b/libs/hwui/PathCache.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <GLES2/gl2.h>
+
+#include <SkCanvas.h>
+#include <SkRect.h>
+
+#include "PathCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+PathCache::PathCache(uint32_t maxByteSize):
+ mCache(GenerationCache<PathCacheEntry, PathTexture*>::kUnlimitedCapacity),
+ mSize(0), mMaxSize(maxByteSize) {
+ mCache.setOnEntryRemovedListener(this);
+}
+
+PathCache::~PathCache() {
+ mCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t PathCache::getSize() {
+ return mSize;
+}
+
+uint32_t PathCache::getMaxSize() {
+ return mMaxSize;
+}
+
+void PathCache::setMaxSize(uint32_t maxSize) {
+ mMaxSize = maxSize;
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::operator()(PathCacheEntry& path, PathTexture*& texture) {
+ const uint32_t size = texture->width * texture->height;
+ mSize -= size;
+
+ if (texture) {
+ glDeleteTextures(1, &texture->id);
+ delete texture;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
+ PathCacheEntry entry(path, paint);
+ PathTexture* texture = mCache.get(entry);
+
+ if (!texture) {
+ texture = addTexture(entry, path, paint);
+ } else if (path->getGenerationID() != texture->generation) {
+ mCache.remove(entry);
+ texture = addTexture(entry, path, paint);
+ }
+
+ // TODO: Do something to destroy the texture object if it's too big for the cache
+ return texture;
+}
+
+PathTexture* PathCache::addTexture(const PathCacheEntry& entry,
+ const SkPath *path, const SkPaint* paint) {
+ const SkRect& bounds = path->getBounds();
+ const float offset = entry.strokeWidth * 1.5f;
+ const uint32_t width = uint32_t(bounds.width() + offset * 2.0 + 0.5);
+ const uint32_t height = uint32_t(bounds.height() + offset * 2.0 + 0.5);
+
+ const uint32_t size = width * height;
+ // Don't even try to cache a bitmap that's bigger than the cache
+ if (size < mMaxSize) {
+ while (mSize + size > mMaxSize) {
+ mCache.removeOldest();
+ }
+ }
+
+ PathTexture* texture = new PathTexture;
+ texture->left = bounds.fLeft;
+ texture->top = bounds.fTop;
+ texture->offset = offset;
+ texture->width = width;
+ texture->height = height;
+ texture->generation = path->getGenerationID();
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kA8_Config, width, height);
+ bitmap.allocPixels();
+ bitmap.eraseColor(0);
+
+ SkCanvas canvas(bitmap);
+ canvas.translate(-bounds.fLeft + offset, -bounds.fTop + offset);
+ canvas.drawPath(*path, *paint);
+
+ generateTexture(bitmap, texture);
+
+ if (size < mMaxSize) {
+ mSize += size;
+ mCache.put(entry, texture);
+ }
+
+ return texture;
+}
+
+void PathCache::clear() {
+ mCache.clear();
+}
+
+void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) {
+ SkAutoLockPixels alp(bitmap);
+ if (!bitmap.readyToDraw()) {
+ LOGE("Cannot generate texture from bitmap");
+ return;
+ }
+
+ glGenTextures(1, &texture->id);
+
+ glBindTexture(GL_TEXTURE_2D, texture->id);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap.bytesPerPixel());
+
+ texture->blend = true;
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, bitmap.rowBytesAsPixels(), texture->height, 0,
+ GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
new file mode 100644
index 0000000..87a9141
--- /dev/null
+++ b/libs/hwui/PathCache.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_PATH_CACHE_H
+#define ANDROID_UI_PATH_CACHE_H
+
+#include <SkBitmap.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+
+#include "Texture.h"
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Describe a path in the path cache.
+ */
+struct PathCacheEntry {
+ PathCacheEntry() {
+ path = NULL;
+ join = SkPaint::kDefault_Join;
+ cap = SkPaint::kDefault_Cap;
+ style = SkPaint::kFill_Style;
+ miter = 4.0f;
+ strokeWidth = 1.0f;
+ }
+
+ PathCacheEntry(const PathCacheEntry& entry):
+ path(entry.path), join(entry.join), cap(entry.cap),
+ style(entry.style), miter(entry.miter),
+ strokeWidth(entry.strokeWidth) {
+ }
+
+ PathCacheEntry(SkPath* path, SkPaint* paint) {
+ this->path = path;
+ join = paint->getStrokeJoin();
+ cap = paint->getStrokeCap();
+ miter = paint->getStrokeMiter();
+ strokeWidth = paint->getStrokeWidth();
+ style = paint->getStyle();
+ }
+
+ SkPath* path;
+ SkPaint::Join join;
+ SkPaint::Cap cap;
+ SkPaint::Style style;
+ float miter;
+ float strokeWidth;
+
+ bool operator<(const PathCacheEntry& rhs) const {
+ return memcmp(this, &rhs, sizeof(PathCacheEntry)) < 0;
+ }
+}; // struct PathCacheEntry
+
+/**
+ * Alpha texture used to represent a path.
+ */
+struct PathTexture: public Texture {
+ /**
+ * Left coordinate of the path bounds.
+ */
+ float left;
+ /**
+ * Top coordinate of the path bounds.
+ */
+ float top;
+ /**
+ * Offset to draw the path at the correct origin.
+ */
+ float offset;
+}; // struct PathTexture
+
+/**
+ * A simple LRU path cache. The cache has a maximum size expressed in bytes.
+ * Any texture added to the cache causing the cache to grow beyond the maximum
+ * allowed size will also cause the oldest texture to be kicked out.
+ */
+class PathCache: public OnEntryRemoved<PathCacheEntry, PathTexture*> {
+public:
+ PathCache(uint32_t maxByteSize);
+ ~PathCache();
+
+ /**
+ * Used as a callback when an entry is removed from the cache.
+ * Do not invoke directly.
+ */
+ void operator()(PathCacheEntry& path, PathTexture*& texture);
+
+ /**
+ * Returns the texture associated with the specified path. If the texture
+ * cannot be found in the cache, a new texture is generated.
+ */
+ PathTexture* get(SkPath* path, SkPaint* paint);
+ /**
+ * Clears the cache. This causes all textures to be deleted.
+ */
+ void clear();
+
+ /**
+ * Sets the maximum size of the cache in bytes.
+ */
+ void setMaxSize(uint32_t maxSize);
+ /**
+ * Returns the maximum size of the cache in bytes.
+ */
+ uint32_t getMaxSize();
+ /**
+ * Returns the current size of the cache in bytes.
+ */
+ uint32_t getSize();
+
+private:
+ /**
+ * Generates the texture from a bitmap into the specified texture structure.
+ */
+ void generateTexture(SkBitmap& bitmap, Texture* texture);
+
+ PathTexture* addTexture(const PathCacheEntry& entry, const SkPath *path, const SkPaint* paint);
+
+ GenerationCache<PathCacheEntry, PathTexture*> mCache;
+
+ uint32_t mSize;
+ uint32_t mMaxSize;
+}; // class PathCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PATH_CACHE_H
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 3205258..8a97b4c 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -90,7 +90,7 @@
};
const char* gFS_Main =
"\nvoid main(void) {\n"
- " vec4 fragColor;\n";
+ " lowp vec4 fragColor;\n";
const char* gFS_Main_FetchColor =
" fragColor = color;\n";
const char* gFS_Main_FetchTexture =
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 4f20a95..915b0be 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -26,10 +26,10 @@
#define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size"
#define PROPERTY_LAYER_CACHE_SIZE "ro.hwui.layer_cache_size"
#define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
+#define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
// These properties are defined in pixels
#define PROPERTY_TEXT_CACHE_WIDTH "ro.hwui.text_cache_width"
#define PROPERTY_TEXT_CACHE_HEIGHT "ro.hwui.text_cache_height"
#endif // ANDROID_UI_PROPERTIES_H
-
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index ef18787..5dc720f 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -152,6 +152,15 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <activity
+ android:name="PathsActivity"
+ android:label="_Paths">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/PathsActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/PathsActivity.java
new file mode 100644
index 0000000..39d9942
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/PathsActivity.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class PathsActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final PathsView view = new PathsView(this);
+ setContentView(view);
+ }
+
+ static class PathsView extends View {
+ private final Bitmap mBitmap1;
+ private final Paint mSmallPaint;
+ private final Paint mMediumPaint;
+ private final Paint mLargePaint;
+ private final BitmapShader mShader;
+ private final Path mPath;
+ private final RectF mPathBounds;
+ private final Paint mBoundsPaint;
+ private final Bitmap mBitmap;
+ private final float mOffset;
+ private final Paint mLinePaint;
+
+ PathsView(Context c) {
+ super(c);
+
+ mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+
+ mSmallPaint = new Paint();
+ mSmallPaint.setAntiAlias(true);
+ mSmallPaint.setColor(0xffff0000);
+ mSmallPaint.setStrokeWidth(1.0f);
+ mSmallPaint.setStyle(Paint.Style.STROKE);
+
+ mLinePaint = new Paint();
+ mLinePaint.setAntiAlias(true);
+ mLinePaint.setColor(0xffff00ff);
+ mLinePaint.setStrokeWidth(1.0f);
+ mLinePaint.setStyle(Paint.Style.STROKE);
+
+ mMediumPaint = new Paint();
+ mMediumPaint.setAntiAlias(true);
+ mMediumPaint.setColor(0xff0000ff);
+ mMediumPaint.setStrokeWidth(10.0f);
+ mMediumPaint.setStyle(Paint.Style.STROKE);
+
+ mLargePaint = new Paint();
+ mLargePaint.setAntiAlias(true);
+ mLargePaint.setColor(0xff00ff00);
+ mLargePaint.setStrokeWidth(15.0f);
+ mLargePaint.setStyle(Paint.Style.FILL);
+
+ mShader = new BitmapShader(mBitmap1, BitmapShader.TileMode.MIRROR,
+ BitmapShader.TileMode.MIRROR);
+
+ mPath = new Path();
+ mPath.moveTo(0.0f, 0.0f);
+ mPath.cubicTo(0.0f, 0.0f, 100.0f, 150.0f, 100.0f, 200.0f);
+ mPath.cubicTo(100.0f, 200.0f, 50.0f, 300.0f, -80.0f, 200.0f);
+ mPath.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
+
+ mPathBounds = new RectF();
+ mPath.computeBounds(mPathBounds, true);
+
+ mBoundsPaint = new Paint();
+ mBoundsPaint.setColor(0x4000ff00);
+
+ mOffset = mMediumPaint.getStrokeWidth();
+ final int width = (int) (mPathBounds.width() + mOffset * 3.0f + 0.5f);
+ final int height = (int) (mPathBounds.height() + mOffset * 3.0f + 0.5f);
+ mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
+ Canvas canvas = new Canvas(mBitmap);
+ canvas.translate(-mPathBounds.left + mOffset * 1.5f, -mPathBounds.top + mOffset * 1.5f);
+ canvas.drawPath(mPath, mMediumPaint);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ canvas.drawARGB(255, 255, 255, 255);
+
+ canvas.save();
+ canvas.translate(200.0f, 60.0f);
+ canvas.drawPath(mPath, mSmallPaint);
+
+ canvas.translate(350.0f, 0.0f);
+ canvas.drawPath(mPath, mMediumPaint);
+
+ mLargePaint.setShader(mShader);
+ canvas.translate(350.0f, 0.0f);
+ canvas.drawPath(mPath, mLargePaint);
+ mLargePaint.setShader(null);
+ canvas.restore();
+
+ canvas.save();
+ canvas.translate(200.0f, 360.0f);
+ canvas.drawPath(mPath, mSmallPaint);
+ canvas.drawRect(mPathBounds, mBoundsPaint);
+
+ canvas.translate(350.0f, 0.0f);
+ canvas.drawBitmap(mBitmap, mPathBounds.left - mOffset * 1.5f,
+ mPathBounds.top - mOffset * 1.5f, null);
+ canvas.drawRect(mPathBounds, mBoundsPaint);
+ canvas.drawLine(0.0f, -360.0f, 0.0f, 500.0f, mLinePaint);
+
+ mLargePaint.setShader(mShader);
+ canvas.translate(350.0f, 0.0f);
+ canvas.drawPath(mPath, mLargePaint);
+ canvas.drawRect(mPathBounds, mBoundsPaint);
+ mLargePaint.setShader(null);
+ canvas.restore();
+ }
+ }
+}