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