OSDN Git Service

Using GL to draw NinePatch
authorOwen Lin <owenlin@google.com>
Wed, 12 May 2010 07:43:14 +0000 (15:43 +0800)
committerOwen Lin <owenlin@google.com>
Mon, 17 May 2010 09:59:51 +0000 (17:59 +0800)
Change-Id: I2aa92bba691cbfde75523c196a880d0e18eeaba4

src/com/android/camera/ui/GLRootView.java
src/com/android/camera/ui/NinePatchChunk.java [new file with mode: 0644]
src/com/android/camera/ui/NinePatchTexture.java
src/com/android/camera/ui/Texture.java

index 3bd5402..dc65c85 100644 (file)
@@ -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.
+     *
+     * <pre>
+     *                      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
+     * </pre>
+     *
+     * @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 (file)
index 0000000..d4611ef
--- /dev/null
@@ -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
index e4cc9ac..1fae5f8 100644 (file)
 
 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);
     }
 }
index 4450720..ba3a3d2 100644 (file)
@@ -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);