Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.ui;
     18 
     19 import com.android.gallery3d.common.Utils;
     20 
     21 import android.content.Context;
     22 import android.graphics.Bitmap;
     23 import android.graphics.BitmapFactory;
     24 import android.graphics.Rect;
     25 
     26 import java.nio.ByteBuffer;
     27 import java.nio.ByteOrder;
     28 import java.nio.FloatBuffer;
     29 import java.util.LinkedHashMap;
     30 import java.util.Map;
     31 import javax.microedition.khronos.opengles.GL11;
     32 
     33 // NinePatchTexture is a texture backed by a NinePatch resource.
     34 //
     35 // getPaddings() returns paddings specified in the NinePatch.
     36 // getNinePatchChunk() returns the layout data specified in the NinePatch.
     37 //
     38 public class NinePatchTexture extends ResourceTexture {
     39     @SuppressWarnings("unused")
     40     private static final String TAG = "NinePatchTexture";
     41     private NinePatchChunk mChunk;
     42     private MyCacheMap<Long, NinePatchInstance> mInstanceCache =
     43             new MyCacheMap<Long, NinePatchInstance>();
     44 
     45     public NinePatchTexture(Context context, int resId) {
     46         super(context, resId);
     47     }
     48 
     49     @Override
     50     protected Bitmap onGetBitmap() {
     51         if (mBitmap != null) return mBitmap;
     52 
     53         BitmapFactory.Options options = new BitmapFactory.Options();
     54         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
     55         Bitmap bitmap = BitmapFactory.decodeResource(
     56                 mContext.getResources(), mResId, options);
     57         mBitmap = bitmap;
     58         setSize(bitmap.getWidth(), bitmap.getHeight());
     59         byte[] chunkData = bitmap.getNinePatchChunk();
     60         mChunk = chunkData == null
     61                 ? null
     62                 : NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
     63         if (mChunk == null) {
     64             throw new RuntimeException("invalid nine-patch image: " + mResId);
     65         }
     66         return bitmap;
     67     }
     68 
     69     public Rect getPaddings() {
     70         // get the paddings from nine patch
     71         if (mChunk == null) onGetBitmap();
     72         return mChunk.mPaddings;
     73     }
     74 
     75     public NinePatchChunk getNinePatchChunk() {
     76         if (mChunk == null) onGetBitmap();
     77         return mChunk;
     78     }
     79 
     80     private static class MyCacheMap<K, V> extends LinkedHashMap<K, V> {
     81         private int CACHE_SIZE = 16;
     82         private V mJustRemoved;
     83 
     84         public MyCacheMap() {
     85             super(4, 0.75f, true);
     86         }
     87 
     88         @Override
     89         protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
     90             if (size() > CACHE_SIZE) {
     91                 mJustRemoved = eldest.getValue();
     92                 return true;
     93             }
     94             return false;
     95         }
     96 
     97         public V getJustRemoved() {
     98             V result = mJustRemoved;
     99             mJustRemoved = null;
    100             return result;
    101         }
    102     }
    103 
    104     private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
    105         long key = w;
    106         key = (key << 32) | h;
    107         NinePatchInstance instance = mInstanceCache.get(key);
    108 
    109         if (instance == null) {
    110             instance = new NinePatchInstance(this, w, h);
    111             mInstanceCache.put(key, instance);
    112             NinePatchInstance removed = mInstanceCache.getJustRemoved();
    113             if (removed != null) {
    114                 removed.recycle(canvas);
    115             }
    116         }
    117 
    118         return instance;
    119     }
    120 
    121     @Override
    122     public void draw(GLCanvas canvas, int x, int y, int w, int h) {
    123         if (!isLoaded(canvas)) {
    124             mInstanceCache.clear();
    125         }
    126 
    127         if (w != 0 && h != 0) {
    128             findInstance(canvas, w, h).draw(canvas, this, x, y);
    129         }
    130     }
    131 
    132     @Override
    133     public void recycle() {
    134         super.recycle();
    135         GLCanvas canvas = mCanvasRef == null ? null : mCanvasRef.get();
    136         if (canvas == null) return;
    137         for (NinePatchInstance instance : mInstanceCache.values()) {
    138             instance.recycle(canvas);
    139         }
    140         mInstanceCache.clear();
    141     }
    142 }
    143 
    144 // This keeps data for a specialization of NinePatchTexture with the size
    145 // (width, height). We pre-compute the coordinates for efficiency.
    146 class NinePatchInstance {
    147 
    148     @SuppressWarnings("unused")
    149     private static final String TAG = "NinePatchInstance";
    150 
    151     // We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
    152     private static final int VERTEX_BUFFER_SIZE = 16 * 2;
    153 
    154     // We need 22 indices for a normal nine-patch image, plus 2 for each
    155     // transparent region. Current there are at most 1 transparent region.
    156     private static final int INDEX_BUFFER_SIZE = 22 + 2;
    157 
    158     private FloatBuffer mXyBuffer;
    159     private FloatBuffer mUvBuffer;
    160     private ByteBuffer mIndexBuffer;
    161 
    162     // Names for buffer names: xy, uv, index.
    163     private int[] mBufferNames;
    164 
    165     private int mIdxCount;
    166 
    167     public NinePatchInstance(NinePatchTexture tex, int width, int height) {
    168         NinePatchChunk chunk = tex.getNinePatchChunk();
    169 
    170         if (width <= 0 || height <= 0) {
    171             throw new RuntimeException("invalid dimension");
    172         }
    173 
    174         // The code should be easily extended to handle the general cases by
    175         // allocating more space for buffers. But let's just handle the only
    176         // use case.
    177         if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
    178             throw new RuntimeException("unsupported nine patch");
    179         }
    180 
    181         float divX[] = new float[4];
    182         float divY[] = new float[4];
    183         float divU[] = new float[4];
    184         float divV[] = new float[4];
    185 
    186         int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
    187         int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
    188 
    189         prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
    190     }
    191 
    192     /**
    193      * Stretches the texture according to the nine-patch rules. It will
    194      * linearly distribute the strechy parts defined in the nine-patch chunk to
    195      * the target area.
    196      *
    197      * <pre>
    198      *                      source
    199      *          /--------------^---------------\
    200      *         u0    u1       u2  u3     u4   u5
    201      * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
    202      *          |    div0    div1 div2   div3  |
    203      *          |     |       /   /      /    /
    204      *          |     |      /   /     /    /
    205      *          |     |     /   /    /    /
    206      *          |fffff|ssss|fff|sss|ffff| ---> x
    207      *         x0    x1   x2  x3  x4   x5
    208      *          \----------v------------/
    209      *                  target
    210      *
    211      * f: fixed segment
    212      * s: stretchy segment
    213      * </pre>
    214      *
    215      * @param div the stretch parts defined in nine-patch chunk
    216      * @param source the length of the texture
    217      * @param target the length on the drawing plan
    218      * @param u output, the positions of these dividers in the texture
    219      *        coordinate
    220      * @param x output, the corresponding position of these dividers on the
    221      *        drawing plan
    222      * @return the number of these dividers.
    223      */
    224     private static int stretch(
    225             float x[], float u[], int div[], int source, int target) {
    226         int textureSize = Utils.nextPowerOf2(source);
    227         float textureBound = (float) source / textureSize;
    228 
    229         float stretch = 0;
    230         for (int i = 0, n = div.length; i < n; i += 2) {
    231             stretch += div[i + 1] - div[i];
    232         }
    233 
    234         float remaining = target - source + stretch;
    235 
    236         float lastX = 0;
    237         float lastU = 0;
    238 
    239         x[0] = 0;
    240         u[0] = 0;
    241         for (int i = 0, n = div.length; i < n; i += 2) {
    242             // Make the stretchy segment a little smaller to prevent sampling
    243             // on neighboring fixed segments.
    244             // fixed segment
    245             x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
    246             u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
    247 
    248             // stretchy segment
    249             float partU = div[i + 1] - div[i];
    250             float partX = remaining * partU / stretch;
    251             remaining -= partX;
    252             stretch -= partU;
    253 
    254             lastX = x[i + 1] + partX;
    255             lastU = div[i + 1];
    256             x[i + 2] = lastX - 0.5f;
    257             u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound);
    258         }
    259         // the last fixed segment
    260         x[div.length + 1] = target;
    261         u[div.length + 1] = textureBound;
    262 
    263         // remove segments with length 0.
    264         int last = 0;
    265         for (int i = 1, n = div.length + 2; i < n; ++i) {
    266             if ((x[i] - x[last]) < 1f) continue;
    267             x[++last] = x[i];
    268             u[last] = u[i];
    269         }
    270         return last + 1;
    271     }
    272 
    273     private void prepareVertexData(float x[], float y[], float u[], float v[],
    274             int nx, int ny, int[] color) {
    275         /*
    276          * Given a 3x3 nine-patch image, the vertex order is defined as the
    277          * following graph:
    278          *
    279          * (0) (1) (2) (3)
    280          *  |  /|  /|  /|
    281          *  | / | / | / |
    282          * (4) (5) (6) (7)
    283          *  | \ | \ | \ |
    284          *  |  \|  \|  \|
    285          * (8) (9) (A) (B)
    286          *  |  /|  /|  /|
    287          *  | / | / | / |
    288          * (C) (D) (E) (F)
    289          *
    290          * And we draw the triangle strip in the following index order:
    291          *
    292          * index: 04152637B6A5948C9DAEBF
    293          */
    294         int pntCount = 0;
    295         float xy[] = new float[VERTEX_BUFFER_SIZE];
    296         float uv[] = new float[VERTEX_BUFFER_SIZE];
    297         for (int j = 0; j < ny; ++j) {
    298             for (int i = 0; i < nx; ++i) {
    299                 int xIndex = (pntCount++) << 1;
    300                 int yIndex = xIndex + 1;
    301                 xy[xIndex] = x[i];
    302                 xy[yIndex] = y[j];
    303                 uv[xIndex] = u[i];
    304                 uv[yIndex] = v[j];
    305             }
    306         }
    307 
    308         int idxCount = 1;
    309         boolean isForward = false;
    310         byte index[] = new byte[INDEX_BUFFER_SIZE];
    311         for (int row = 0; row < ny - 1; row++) {
    312             --idxCount;
    313             isForward = !isForward;
    314 
    315             int start, end, inc;
    316             if (isForward) {
    317                 start = 0;
    318                 end = nx;
    319                 inc = 1;
    320             } else {
    321                 start = nx - 1;
    322                 end = -1;
    323                 inc = -1;
    324             }
    325 
    326             for (int col = start; col != end; col += inc) {
    327                 int k = row * nx + col;
    328                 if (col != start) {
    329                     int colorIdx = row * (nx - 1) + col;
    330                     if (isForward) colorIdx--;
    331                     if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
    332                         index[idxCount] = index[idxCount - 1];
    333                         ++idxCount;
    334                         index[idxCount++] = (byte) k;
    335                     }
    336                 }
    337 
    338                 index[idxCount++] = (byte) k;
    339                 index[idxCount++] = (byte) (k + nx);
    340             }
    341         }
    342 
    343         mIdxCount = idxCount;
    344 
    345         int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
    346         mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
    347         mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
    348         mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
    349 
    350         mXyBuffer.put(xy, 0, pntCount * 2).position(0);
    351         mUvBuffer.put(uv, 0, pntCount * 2).position(0);
    352         mIndexBuffer.put(index, 0, idxCount).position(0);
    353     }
    354 
    355     private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
    356         return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
    357     }
    358 
    359     private void prepareBuffers(GLCanvas canvas) {
    360         mBufferNames = new int[3];
    361         GL11 gl = canvas.getGLInstance();
    362         gl.glGenBuffers(3, mBufferNames, 0);
    363 
    364         gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBufferNames[0]);
    365         gl.glBufferData(GL11.GL_ARRAY_BUFFER,
    366                 mXyBuffer.capacity() * (Float.SIZE / Byte.SIZE),
    367                 mXyBuffer, GL11.GL_STATIC_DRAW);
    368 
    369         gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBufferNames[1]);
    370         gl.glBufferData(GL11.GL_ARRAY_BUFFER,
    371                 mUvBuffer.capacity() * (Float.SIZE / Byte.SIZE),
    372                 mUvBuffer, GL11.GL_STATIC_DRAW);
    373 
    374         gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mBufferNames[2]);
    375         gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER,
    376                 mIndexBuffer.capacity(),
    377                 mIndexBuffer, GL11.GL_STATIC_DRAW);
    378 
    379         // These buffers are never used again.
    380         mXyBuffer = null;
    381         mUvBuffer = null;
    382         mIndexBuffer = null;
    383     }
    384 
    385     public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
    386         if (mBufferNames == null) {
    387             prepareBuffers(canvas);
    388         }
    389         canvas.drawMesh(tex, x, y, mBufferNames[0], mBufferNames[1],
    390                 mBufferNames[2], mIdxCount);
    391     }
    392 
    393     public void recycle(GLCanvas canvas) {
    394         if (mBufferNames != null) {
    395             canvas.deleteBuffer(mBufferNames[0]);
    396             canvas.deleteBuffer(mBufferNames[1]);
    397             canvas.deleteBuffer(mBufferNames[2]);
    398             mBufferNames = null;
    399         }
    400     }
    401 }
    402