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