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