package com.glview.hwui.cache; import java.util.Arrays; import android.graphics.Bitmap; import android.support.v4.util.LruCache; import com.glview.graphics.drawable.ninepatch.NinePatch; import com.glview.graphics.drawable.ninepatch.NinePatchChunk; import com.glview.libgdx.graphics.Mesh; import com.glview.libgdx.graphics.Mesh.VertexDataType; import com.glview.libgdx.graphics.VertexAttribute; import com.glview.libgdx.graphics.VertexAttributes.Usage; import com.glview.libgdx.graphics.glutils.ShaderProgram; public class PatchCache { final static int DEFAULT_PATCH_CACHE_SIZE = 128 * 1024; //128KB // We need 16 vertices for a normal nine-patch image (the 4x4 vertices) private static final int VERTEX_BUFFER_SIZE = 36 * 2;//16 * 2; // We need 22 indices for a normal nine-patch image, plus 2 for each // transparent region. Current there are at most 1 transparent region. private static final int INDEX_BUFFER_SIZE = 56 + 2;//22 + 2; final float mTmpVertices[] = new float[4*VERTEX_BUFFER_SIZE/2]; final short mTmpIndices[] = new short[INDEX_BUFFER_SIZE]; private int mTmpIndicesCount = -1; private int mTmpVerticesCount = -1; final float mDivX[] = new float[8]; final float mDivY[] = new float[8]; final float mDivU[] = new float[8]; final float mDivV[] = new float[8]; PatchLruCache mCache; public PatchCache() { mCache = new PatchLruCache(DEFAULT_PATCH_CACHE_SIZE); } public void clear() { mCache.evictAll(); } public Mesh get(int drawWidth, int drawHeight, NinePatch ninePatch) { String key = "" + drawWidth + "*" + drawHeight + "+" + ninePatch.getBitmap(); Mesh mesh = mCache.get(key); if (mesh == null) { mesh = create(drawWidth, drawHeight, ninePatch); if (mesh != null) { mCache.put(key, mesh); } } return mesh; } private Mesh create(int width, int height, NinePatch patch) { NinePatchChunk chunk = patch.getChunk(); int drawWidth = width; int drawHeight = height; if (drawWidth < 0 || drawHeight < 0) return null; int nx = stretch(mDivX, mDivU, chunk.mDivX, patch.getBitmap().getWidth(), drawWidth, patch.getBitmap().getWidth()); int ny = stretch(mDivY, mDivV, chunk.mDivY, patch.getBitmap().getHeight(), drawHeight, patch.getBitmap().getHeight()); prepareVertexData(mDivX, mDivY, mDivU, mDivV, nx, ny, chunk.mColor); Mesh mesh = new Mesh(VertexDataType.VertexArray, false, mTmpVerticesCount, mTmpIndicesCount, new VertexAttribute(Usage.Position, 2, ShaderProgram.POSITION_ATTRIBUTE), new VertexAttribute(Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE)); mesh.setIndices(mTmpIndices, 0, mTmpIndicesCount); mesh.setVertices(mTmpVertices, 0, mTmpVerticesCount * 4); return mesh; } private void prepareVertexData(float x[], float y[], float u[], float v[], int nx, int ny, int[] color) { /* * Given a 3x3 nine-patch image, the vertex order is defined as the * following graph: * * (0) (1) (2) (3) | /| /| /| | / | / | / | (4) (5) (6) (7) | \ | \ | \ * | | \| \| \| (8) (9) (A) (B) | /| /| /| | / | / | / | (C) (D) (E) (F) * * And we draw the triangle strip in the following index order: * * index: 04152637B6A5948C9DAEBF */ int pntCount = 0; for (int j = 0; j < ny; ++j) { for (int i = 0; i < nx; ++i) { mTmpVertices[pntCount * 4 + 0] = x[i]; mTmpVertices[pntCount * 4 + 1] = y[j]; mTmpVertices[pntCount * 4 + 2] = u[i]; mTmpVertices[pntCount * 4 + 3] = v[j]; pntCount++; } } mTmpVerticesCount = pntCount; Arrays.fill(mTmpIndices, (short) -1); int idxCount = 1; boolean isForward = false; for (int row = 0; row < ny - 1; row++) { --idxCount; isForward = !isForward; int start, end, inc; if (isForward) { start = 0; end = nx; inc = 1; } else { start = nx - 1; end = -1; inc = -1; } for (int col = start; col != end; col += inc) { int k = row * nx + col; mTmpIndices[idxCount++] = (byte) k; mTmpIndices[idxCount++] = (byte) (k + nx); } } mTmpIndicesCount = idxCount; } /** * Stretches the texture according to the nine-patch rules. It will * linearly distribute the strechy parts defined in the nine-patch chunk to * the target area. * * <pre> * source * /--------------^---------------\ * u0 u1 u2 u3 u4 u5 * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u * | div0 div1 div2 div3 | * | | / / / / * | | / / / / * | | / / / / * |fffff|ssss|fff|sss|ffff| ---> x * x0 x1 x2 x3 x4 x5 * \----------v------------/ * target * * f: fixed segment * s: stretchy segment * </pre> * * @param div the stretch parts defined in nine-patch chunk * @param source the length of the texture * @param target the length on the drawing plan * @param u output, the positions of these dividers in the texture * coordinate * @param x output, the corresponding position of these dividers on the * drawing plan * @return the number of these dividers. */ private static int stretch( float x[], float u[], int div[], int source, int target, int sourceTextureSize) { int textureSize = sourceTextureSize; float textureBound = (float) source / textureSize; float stretch = 0; for (int i = 0, n = div.length; i < n; i += 2) { stretch += div[i + 1] - div[i]; } float remaining = target - source + stretch; float lastX = 0; float lastU = 0; x[0] = 0; u[0] = 0; for (int i = 0, n = div.length; i < n; i += 2) { // Make the stretchy segment a little smaller to prevent sampling // on neighboring fixed segments. // fixed segment x[i + 1] = lastX + (div[i] - lastU) + 0.5f; u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound); // stretchy segment float partU = div[i + 1] - div[i]; float partX = remaining * partU / stretch; remaining -= partX; stretch -= partU; lastX = x[i + 1] + partX; lastU = div[i + 1]; x[i + 2] = lastX - 0.5f; u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound); } // the last fixed segment x[div.length + 1] = target; u[div.length + 1] = textureBound; // remove segments with length 0. int last = 0; for (int i = 1, n = div.length + 2; i < n; ++i) { if ((x[i] - x[last]) < 1f) continue; x[++last] = x[i]; u[last] = u[i]; } return last + 1; } class PatchLruCacheKey { int mDrawWidth, mDrawHeight; Bitmap mBitmap; } class PatchLruCache extends LruCache<Object, Mesh> { public PatchLruCache(int maxSize) { super(maxSize); } /** * indices byte count + vertices byte count */ @Override protected int sizeOf(Object key, Mesh value) { // Short type index, Multiplied by 2., return value.getNumIndices() * 2 + value.getNumVertices() * value.getVertexSize(); } @Override protected void entryRemoved(boolean evicted, Object key, Mesh oldValue, Mesh newValue) { oldValue.dispose(); } @Override protected Mesh create(Object key) { return null; } @Override protected void finalize() throws Throwable { super.finalize(); evictAll(); } } }