From e4caca202b8dbec941a900bf470534ffc0436ce1 Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Wed, 12 May 2010 15:43:14 +0800 Subject: [PATCH] Using GL to draw NinePatch Change-Id: I2aa92bba691cbfde75523c196a880d0e18eeaba4 --- src/com/android/camera/ui/GLRootView.java | 245 +++++++++++++++++++++--- src/com/android/camera/ui/NinePatchChunk.java | 66 +++++++ src/com/android/camera/ui/NinePatchTexture.java | 102 ++++------ src/com/android/camera/ui/Texture.java | 10 +- 4 files changed, 336 insertions(+), 87 deletions(-) create mode 100644 src/com/android/camera/ui/NinePatchChunk.java diff --git a/src/com/android/camera/ui/GLRootView.java b/src/com/android/camera/ui/GLRootView.java index 3bd5402..dc65c85 100644 --- a/src/com/android/camera/ui/GLRootView.java +++ b/src/com/android/camera/ui/GLRootView.java @@ -59,7 +59,11 @@ public class GLRootView extends GLSurfaceView private int mFrameCount = 0; private long mFrameCountingStart = 0; - private static final int VERTEX_BUFFER_SIZE = 8; + // We need 16 vertices for a normal nine-patch image (the 4x4 vertices) + private static final int VERTEX_BUFFER_SIZE = 16 * 2; + + // We need 22 indices for a normal nine-patch image + private static final int INDEX_BUFFER_SIZE = 22; private static final int FLAG_INITIALIZED = 1; private static final int FLAG_NEED_LAYOUT = 2; @@ -85,11 +89,18 @@ public class GLRootView extends GLSurfaceView private final float mMatrixValues[] = new float[16]; - private final float mCoordBuffer[] = new float[8]; - private final float mPointBuffer[] = new float[4]; + private final float mUvBuffer[] = new float[VERTEX_BUFFER_SIZE]; + private final float mXyBuffer[] = new float[VERTEX_BUFFER_SIZE]; + private final byte mIndexBuffer[] = new byte[INDEX_BUFFER_SIZE]; + + private int mNinePatchX[] = new int[4]; + private int mNinePatchY[] = new int[4]; + private float mNinePatchU[] = new float[4]; + private float mNinePatchV[] = new float[4]; - private ByteBuffer mVertexBuffer; - private ByteBuffer mTexCoordBuffer; + private ByteBuffer mXyPointer; + private ByteBuffer mUvPointer; + private ByteBuffer mIndexPointer; private int mFlags = FLAG_NEED_LAYOUT; private long mAnimationTime; @@ -165,6 +176,10 @@ public class GLRootView extends GLSurfaceView return mEglConfigChooser; } + private static ByteBuffer allocateDirectNativeOrderBuffer(int size) { + return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + } + private void initialize() { mFlags |= FLAG_INITIALIZED; setEGLConfigChooser(mEglConfigChooser); @@ -173,15 +188,10 @@ public class GLRootView extends GLSurfaceView setRenderer(this); - mVertexBuffer = ByteBuffer - .allocateDirect(VERTEX_BUFFER_SIZE * Float.SIZE / Byte.SIZE) - .order(ByteOrder.nativeOrder()); - mVertexBuffer.asFloatBuffer() - .put(new float[] {0, 0, 1, 0, 0, 1, 1, 1}) - .position(0); - mTexCoordBuffer = ByteBuffer - .allocateDirect(VERTEX_BUFFER_SIZE * Float.SIZE / Byte.SIZE) - .order(ByteOrder.nativeOrder()); + int size = VERTEX_BUFFER_SIZE * Float.SIZE / Byte.SIZE; + mXyPointer = allocateDirectNativeOrderBuffer(size); + mUvPointer = allocateDirectNativeOrderBuffer(size); + mIndexPointer = allocateDirectNativeOrderBuffer(INDEX_BUFFER_SIZE); } public void setContentPane(GLView content) { @@ -264,8 +274,10 @@ public class GLRootView extends GLSurfaceView // Set the background color gl.glClearColor(0f, 0f, 0f, 0f); gl.glClearStencil(0); - gl.glVertexPointer(2, GL11.GL_FLOAT, 0, mVertexBuffer); - gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, mTexCoordBuffer); + + gl.glVertexPointer(2, GL11.GL_FLOAT, 0, mXyPointer); + gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, mUvPointer); + } /** @@ -311,21 +323,210 @@ public class GLRootView extends GLSurfaceView drawRect(x, y, width, height, matrix); } + private static void putRectengle(float x, float y, + float width, float height, float[] buffer, ByteBuffer pointer) { + buffer[0] = x; + buffer[1] = y; + buffer[2] = x + width; + buffer[3] = y; + buffer[4] = x; + buffer[5] = y + height; + buffer[6] = x + width; + buffer[7] = y + height; + pointer.asFloatBuffer().put(buffer, 0, 8).position(0); + } + private void drawRect( int x, int y, int width, int height, float matrix[]) { GL11 gl = mGL; gl.glPushMatrix(); gl.glMultMatrixf(toGLMatrix(matrix), 0); - gl.glTranslatef(x, y, 0); - gl.glScalef(width, height, 1); + putRectengle(x, y, width, height, mXyBuffer, mXyPointer); gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 4); gl.glPopMatrix(); } + public void drawNinePatch( + NinePatchTexture tex, int x, int y, int width, int height) { + + NinePatchChunk chunk = tex.getNinePatchChunk(); + + // The code should be easily extended to handle the general cases by + // allocating more space for buffers. But let's just handle the only + // use case. + if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) { + throw new RuntimeException("unsupported nine patch"); + } + if (!tex.bind(this, mGL)) { + throw new RuntimeException("cannot bind" + tex.toString()); + } + if (width <= 0 || height <= 0) return ; + + int divX[] = mNinePatchX; + int divY[] = mNinePatchY; + float divU[] = mNinePatchU; + float divV[] = mNinePatchV; + + int nx = stretch(divX, divU, chunk.mDivX, tex.getIntrinsicWidth(), width); + int ny = stretch(divY, divV, chunk.mDivY, tex.getIntrinsicHeight(), height); + + setAlphaValue(mTransformation.getAlpha()); + Matrix matrix = mTransformation.getMatrix(); + matrix.getValues(mMatrixValues); + GL11 gl = mGL; + gl.glPushMatrix(); + gl.glMultMatrixf(toGLMatrix(mMatrixValues), 0); + gl.glTranslatef(x, y, 0); + drawMesh(divX, divY, divU, divV, nx, ny); + gl.glPopMatrix(); + } + + /** + * Stretches the texture according to the nine-patch rules. It will + * linearly distribute the strechy parts defined in the nine-patch chunk to + * the target area. + * + *
+     *                      source
+     *          /--------------^---------------\
+     *         u0    u1       u2  u3     u4   u5
+     * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
+     *          |    div0    div1 div2   div3  |
+     *          |     |       /   /      /    /
+     *          |     |      /   /     /    /
+     *          |     |     /   /    /    /
+     *          |fffff|ssss|fff|sss|ffff| ---> x
+     *         x0    x1   x2  x3  x4   x5
+     *          \----------v------------/
+     *                  target
+     *
+     * f: fixed segment
+     * s: stretchy segment
+     * 
+ * + * @param div the stretch parts defined in nine-patch chunk + * @param source the length of the texture + * @param target the length on the drawing plan + * @param u output, the positions of these dividers in the texture + * coordinate + * @param x output, the corresponding position of these dividers on the + * drawing plan + * @return the number of these dividers. + */ + private int stretch( + int x[], float u[], int div[], int source, int target) { + int textureSize = Util.nextPowerOf2(source); + float textureBound = (source - 0.5f) / textureSize; + + int stretch = 0; + for (int i = 0, n = div.length; i < n; i += 2) { + stretch += div[i + 1] - div[i]; + } + + float remaining = target - source + stretch; + + int lastX = 0; + int lastU = 0; + + x[0] = 0; + u[0] = 0; + for (int i = 0, n = div.length; i < n; i += 2) { + // fixed segment + x[i + 1] = lastX + (div[i] - lastU); + u[i + 1] = Math.min((float) div[i] / textureSize, textureBound); + + // stretchy segment + float partU = div[i + 1] - div[i]; + int partX = (int)(remaining * partU / stretch + 0.5f); + remaining -= partX; + stretch -= partU; + + lastX = x[i + 1] + partX; + lastU = div[i + 1]; + x[i + 2] = lastX; + u[i + 2] = Math.min((float) lastU / textureSize, textureBound); + } + // the last fixed segment + x[div.length + 1] = target; + u[div.length + 1] = textureBound; + + // remove segments with length 0. + int last = 0; + for (int i = 1, n = div.length + 2; i < n; ++i) { + if (x[last] == x[i]) continue; + x[++last] = x[i]; + u[last] = u[i]; + } + return last + 1; + } + + private void drawMesh( + int x[], int y[], float u[], float v[], int nx, int ny) { + /* + * Given a 3x3 nine-patch image, the vertex order is defined as the + * following graph: + * + * (0) (1) (2) (3) + * | /| /| /| + * | / | / | / | + * (4) (5) (6) (7) + * | \ | \ | \ | + * | \| \| \| + * (8) (9) (A) (B) + * | /| /| /| + * | / | / | / | + * (C) (D) (E) (F) + * + * And we draw the triangle strip in the following index order: + * + * index: 04152637B6A5948C9DAEBF + */ + int pntCount = 0; + float xy[] = mXyBuffer; + float uv[] = mUvBuffer; + for (int j = 0; j < ny; ++j) { + for (int i = 0; i < nx; ++i) { + int xIndex = (pntCount++) << 1; + int yIndex = xIndex + 1; + xy[xIndex] = x[i]; + xy[yIndex] = y[j]; + uv[xIndex] = u[i]; + uv[yIndex] = v[j]; + } + } + mUvPointer.asFloatBuffer().put(uv, 0, pntCount << 1).position(0); + mXyPointer.asFloatBuffer().put(xy, 0, pntCount << 1).position(0); + + int idxCount = 1; + byte index[] = mIndexBuffer; + for (int i = 0, bound = nx * (ny - 1); true;) { + // normal direction + --idxCount; + for (int j = 0; j < nx; ++j, ++i) { + index[idxCount++] = (byte) i; + index[idxCount++] = (byte) (i + nx); + } + if (i >= bound) break; + + // reverse direction + int sum = i + i + nx - 1; + --idxCount; + for (int j = 0; j < nx; ++j, ++i) { + index[idxCount++] = (byte) (sum - i); + index[idxCount++] = (byte) (sum - i + nx); + } + if (i >= bound) break; + } + mIndexPointer.put(index, 0, idxCount).position(0); + + mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP, + idxCount, GL11.GL_UNSIGNED_BYTE, mIndexPointer); + } + private float[] mapPoints(Matrix matrix, int x1, int y1, int x2, int y2) { - float[] point = mPointBuffer; + float[] point = mXyBuffer; point[0] = x1; point[1] = y1; point[2] = x2; point[3] = y2; - matrix.mapPoints(point); + matrix.mapPoints(point, 0, point, 0, 4); return point; } @@ -403,8 +604,8 @@ public class GLRootView extends GLSurfaceView // Test whether it has been rotated or flipped, if so, glDrawTexiOES // won't work if (isMatrixRotatedOrFlipped(mMatrixValues)) { - texture.getTextureCoords(mCoordBuffer, 0); - mTexCoordBuffer.asFloatBuffer().put(mCoordBuffer).position(0); + texture.getTextureCoords(mUvBuffer, 0); + mUvPointer.asFloatBuffer().put(mUvBuffer, 0, 8).position(0); setAlphaValue(alpha); drawRect(x, y, width, height, mMatrixValues); } else { diff --git a/src/com/android/camera/ui/NinePatchChunk.java b/src/com/android/camera/ui/NinePatchChunk.java new file mode 100644 index 0000000..d4611ef --- /dev/null +++ b/src/com/android/camera/ui/NinePatchChunk.java @@ -0,0 +1,66 @@ +package com.android.camera.ui; + +import android.graphics.Rect; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +// See "frameworks/base/include/utils/ResourceTypes.h" for the format of +// NinePatch chunk. +class NinePatchChunk { + + public static final int NO_COLOR = 0x00000001; + public static final int TRANSPARENT_COLOR = 0x00000000; + + public Rect mPaddings = new Rect(); + + public int mDivX[]; + public int mDivY[]; + public int mColor[]; + + private static void readIntArray(int[] data, ByteBuffer buffer) { + for (int i = 0, n = data.length; i < n; ++i) { + data[i] = buffer.getInt(); + } + } + + private static void checkDivCount(int length) { + if (length == 0 || (length & 0x01) != 0) { + throw new RuntimeException("invalid nine-patch: " + length); + } + } + + public static NinePatchChunk deserialize(byte[] data) { + ByteBuffer byteBuffer = + ByteBuffer.wrap(data).order(ByteOrder.nativeOrder()); + + byte wasSerialized = byteBuffer.get(); + if (wasSerialized == 0) return null; + + NinePatchChunk chunk = new NinePatchChunk(); + chunk.mDivX = new int[byteBuffer.get()]; + chunk.mDivY = new int[byteBuffer.get()]; + chunk.mColor = new int[byteBuffer.get()]; + + checkDivCount(chunk.mDivX.length); + checkDivCount(chunk.mDivY.length); + + // skip 8 bytes + byteBuffer.getInt(); + byteBuffer.getInt(); + + chunk.mPaddings.left = byteBuffer.getInt(); + chunk.mPaddings.right = byteBuffer.getInt(); + chunk.mPaddings.top = byteBuffer.getInt(); + chunk.mPaddings.bottom = byteBuffer.getInt(); + + // skip 4 bytes + byteBuffer.getInt(); + + readIntArray(chunk.mDivX, byteBuffer); + readIntArray(chunk.mDivY, byteBuffer); + readIntArray(chunk.mColor, byteBuffer); + + return chunk; + } +} \ No newline at end of file diff --git a/src/com/android/camera/ui/NinePatchTexture.java b/src/com/android/camera/ui/NinePatchTexture.java index e4cc9ac..1fae5f8 100644 --- a/src/com/android/camera/ui/NinePatchTexture.java +++ b/src/com/android/camera/ui/NinePatchTexture.java @@ -16,27 +16,21 @@ package com.android.camera.ui; +import com.android.camera.Util; + import android.content.Context; import android.graphics.Bitmap; -import android.graphics.Canvas; +import android.graphics.BitmapFactory; import android.graphics.Rect; -import android.graphics.drawable.NinePatchDrawable; - -import javax.microedition.khronos.opengles.GL11; class NinePatchTexture extends FrameTexture { - - private MyTexture mDelegate; - - private NinePatchDrawable mNinePatch; - private final Context mContext; private final int mResId; - private int mLastWidth = -1; - private int mLastHeight = -1; - - private final Rect mPaddings = new Rect(); + private Bitmap mBitmap; + private NinePatchChunk mChunk; + private int mIntrinsicWidth = -1; + private int mIntrinsicHeight = -1; public NinePatchTexture(Context context, int resId) { this.mContext = context; @@ -44,71 +38,59 @@ class NinePatchTexture extends FrameTexture { } @Override - public void setSize(int width, int height) { - super.setSize(width, height); - } - - @Override - protected boolean bind(GLRootView root, GL11 gl) { - if (mLastWidth != mWidth || mLastHeight != mHeight) { - if (mDelegate != null) mDelegate.deleteFromGL(); - mDelegate = new MyTexture(mWidth, mHeight); - mLastWidth = mWidth; - mLastHeight = mHeight; - } - return mDelegate.bind(root, gl); - } - - @Override public void getTextureCoords(float coord[], int offset) { - mDelegate.getTextureCoords(coord, offset); - } - - protected NinePatchDrawable getNinePatch() { - if (mNinePatch == null) { - mNinePatch = (NinePatchDrawable) - mContext.getResources().getDrawable(mResId); - mNinePatch.getPadding(mPaddings); - } - return mNinePatch; + throw new UnsupportedOperationException(); } - private class MyTexture extends CanvasTexture { - - public MyTexture(int width, int height) { - super(width, height); - } - - @Override - protected void onDraw (Canvas canvas, Bitmap backing) { - NinePatchDrawable npd = getNinePatch(); - npd.setBounds(0, 0, mWidth, mHeight); - npd.draw(canvas); + @Override + protected Bitmap getBitmap() { + if (mBitmap != null) return mBitmap; + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bitmap = BitmapFactory.decodeResource( + mContext.getResources(), mResId, options); + mBitmap = bitmap; + mIntrinsicWidth = bitmap.getWidth(); + mIntrinsicHeight = bitmap.getHeight(); + mChunk = NinePatchChunk.deserialize(bitmap.getNinePatchChunk()); + if (mChunk == null) { + throw new RuntimeException("invalid nine-patch image: " + mResId); } + return bitmap; } @Override protected void freeBitmap(Bitmap bitmap) { - mDelegate.freeBitmap(bitmap); - } - - @Override - protected Bitmap getBitmap() { - return mDelegate.getBitmap(); + Util.Assert(bitmap == mBitmap); + mBitmap.recycle(); + mBitmap = null; } public int getIntrinsicWidth() { - return getNinePatch().getIntrinsicWidth(); + if (mIntrinsicWidth < 0) getBitmap(); + return mIntrinsicWidth; } public int getIntrinsicHeight() { - return getNinePatch().getIntrinsicHeight(); + if (mIntrinsicHeight < 0) getBitmap(); + return mIntrinsicHeight; } @Override public Rect getPaddings() { // get the paddings from nine patch - if (mNinePatch == null) getNinePatch(); - return mPaddings; + if (mChunk == null) getBitmap(); + return mChunk.mPaddings; + } + + public NinePatchChunk getNinePatchChunk() { + if (mChunk == null) getBitmap(); + return mChunk; + } + + @Override + public void draw(GLRootView root, int x, int y) { + root.drawNinePatch(this, x, y, mWidth, mHeight); } } diff --git a/src/com/android/camera/ui/Texture.java b/src/com/android/camera/ui/Texture.java index 4450720..ba3a3d2 100644 --- a/src/com/android/camera/ui/Texture.java +++ b/src/com/android/camera/ui/Texture.java @@ -42,8 +42,8 @@ abstract class Texture { protected int mWidth = UNSPECIFIED; protected int mHeight = UNSPECIFIED; - private int mTextureWidth; - private int mTextureHeight; + protected int mTextureWidth; + protected int mTextureHeight; protected Texture(GL11 gl, int id, int state) { mGL = gl; @@ -101,7 +101,9 @@ abstract class Texture { try { // Define a vertically flipped crop rectangle for // OES_draw_texture. - int[] cropRect = {0, mHeight, mWidth, - mHeight}; + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int[] cropRect = {0, height, width, -height}; // Upload the bitmap to a new texture. gl.glGenTextures(1, textureId, 0); @@ -117,8 +119,6 @@ abstract class Texture { gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); int widthExt = Util.nextPowerOf2(width); int heightExt = Util.nextPowerOf2(height); int format = GLUtils.getInternalFormat(bitmap); -- 2.11.0