• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
NinePatchTexture(Context context, int resId)44     public NinePatchTexture(Context context, int resId) {
45         super(context, resId);
46     }
47 
48     @Override
onGetBitmap()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 
getPaddings()68     public Rect getPaddings() {
69         // get the paddings from nine patch
70         if (mChunk == null) onGetBitmap();
71         return mChunk.mPaddings;
72     }
73 
getNinePatchChunk()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.
put(int key, V value)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 
get(int key)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 
clear()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 
size()133         public int size() {
134             return mCount;
135         }
136 
valueAt(int i)137         public V valueAt(int i) {
138             return mValue[i];
139         }
140     }
141 
findInstance(GLCanvas canvas, int w, int h)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
draw(GLCanvas canvas, int x, int y, int w, int h)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
recycle()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 
NinePatchInstance(NinePatchTexture tex, int width, int height)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      */
stretch( float x[], float u[], int div[], int source, int target)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 
prepareVertexData(float x[], float y[], float u[], float v[], int nx, int ny, int[] color)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 
allocateDirectNativeOrderBuffer(int size)394     private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
395         return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
396     }
397 
prepareBuffers(GLCanvas canvas)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 
draw(GLCanvas canvas, NinePatchTexture tex, int x, int y)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 
recycle(GLCanvas canvas)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