1 2/* 3 * Copyright 2006 The Android Open Source Project 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9 10#include "SkNinePatch.h" 11#include "SkCanvas.h" 12#include "SkShader.h" 13 14static const uint16_t g3x3Indices[] = { 15 0, 5, 1, 0, 4, 5, 16 1, 6, 2, 1, 5, 6, 17 2, 7, 3, 2, 6, 7, 18 19 4, 9, 5, 4, 8, 9, 20 5, 10, 6, 5, 9, 10, 21 6, 11, 7, 6, 10, 11, 22 23 8, 13, 9, 8, 12, 13, 24 9, 14, 10, 9, 13, 14, 25 10, 15, 11, 10, 14, 15 26}; 27 28static int fillIndices(uint16_t indices[], int xCount, int yCount) { 29 uint16_t* startIndices = indices; 30 31 int n = 0; 32 for (int y = 0; y < yCount; y++) { 33 for (int x = 0; x < xCount; x++) { 34 *indices++ = n; 35 *indices++ = n + xCount + 2; 36 *indices++ = n + 1; 37 38 *indices++ = n; 39 *indices++ = n + xCount + 1; 40 *indices++ = n + xCount + 2; 41 42 n += 1; 43 } 44 n += 1; 45 } 46 return static_cast<int>(indices - startIndices); 47} 48 49// Computes the delta between vertices along a single axis 50static SkScalar computeVertexDelta(bool isStretchyVertex, 51 SkScalar currentVertex, 52 SkScalar prevVertex, 53 SkScalar stretchFactor) { 54 // the standard delta between vertices if no stretching is required 55 SkScalar delta = currentVertex - prevVertex; 56 57 // if the stretch factor is negative or zero we need to shrink the 9-patch 58 // to fit within the target bounds. This means that we will eliminate all 59 // stretchy areas and scale the fixed areas to fit within the target bounds. 60 if (stretchFactor <= 0) { 61 if (isStretchyVertex) 62 delta = 0; // collapse stretchable areas 63 else 64 delta = SkScalarMul(delta, -stretchFactor); // scale fixed areas 65 // if the stretch factor is positive then we use the standard delta for 66 // fixed and scale the stretchable areas to fill the target bounds. 67 } else if (isStretchyVertex) { 68 delta = SkScalarMul(delta, stretchFactor); 69 } 70 71 return delta; 72} 73 74static void fillRow(SkPoint verts[], SkPoint texs[], 75 const SkScalar vy, const SkScalar ty, 76 const SkRect& bounds, const int32_t xDivs[], int numXDivs, 77 const SkScalar stretchX, int width) { 78 SkScalar vx = bounds.fLeft; 79 verts->set(vx, vy); verts++; 80 texs->set(0, ty); texs++; 81 82 SkScalar prev = 0; 83 for (int x = 0; x < numXDivs; x++) { 84 85 const SkScalar tx = SkIntToScalar(xDivs[x]); 86 vx += computeVertexDelta(x & 1, tx, prev, stretchX); 87 prev = tx; 88 89 verts->set(vx, vy); verts++; 90 texs->set(tx, ty); texs++; 91 } 92 verts->set(bounds.fRight, vy); verts++; 93 texs->set(SkIntToScalar(width), ty); texs++; 94} 95 96struct Mesh { 97 const SkPoint* fVerts; 98 const SkPoint* fTexs; 99 const SkColor* fColors; 100 const uint16_t* fIndices; 101}; 102 103void SkNinePatch::DrawMesh(SkCanvas* canvas, const SkRect& bounds, 104 const SkBitmap& bitmap, 105 const int32_t xDivs[], int numXDivs, 106 const int32_t yDivs[], int numYDivs, 107 const SkPaint* paint) { 108 if (bounds.isEmpty() || bitmap.width() == 0 || bitmap.height() == 0) { 109 return; 110 } 111 112 // should try a quick-reject test before calling lockPixels 113 SkAutoLockPixels alp(bitmap); 114 // after the lock, it is valid to check 115 if (!bitmap.readyToDraw()) { 116 return; 117 } 118 119 // check for degenerate divs (just an optimization, not required) 120 { 121 int i; 122 int zeros = 0; 123 for (i = 0; i < numYDivs && yDivs[i] == 0; i++) { 124 zeros += 1; 125 } 126 numYDivs -= zeros; 127 yDivs += zeros; 128 for (i = numYDivs - 1; i >= 0 && yDivs[i] == bitmap.height(); --i) { 129 numYDivs -= 1; 130 } 131 } 132 133 Mesh mesh; 134 135 const int numXStretch = (numXDivs + 1) >> 1; 136 const int numYStretch = (numYDivs + 1) >> 1; 137 138 if (numXStretch < 1 && numYStretch < 1) { 139 canvas->drawBitmapRect(bitmap, NULL, bounds, paint); 140 return; 141 } 142 143 if (false) { 144 int i; 145 for (i = 0; i < numXDivs; i++) { 146 SkDebugf("--- xdivs[%d] %d\n", i, xDivs[i]); 147 } 148 for (i = 0; i < numYDivs; i++) { 149 SkDebugf("--- ydivs[%d] %d\n", i, yDivs[i]); 150 } 151 } 152 153 SkScalar stretchX = 0, stretchY = 0; 154 155 if (numXStretch > 0) { 156 int stretchSize = 0; 157 for (int i = 1; i < numXDivs; i += 2) { 158 stretchSize += xDivs[i] - xDivs[i-1]; 159 } 160 const SkScalar fixed = SkIntToScalar(bitmap.width() - stretchSize); 161 if (bounds.width() >= fixed) 162 stretchX = (bounds.width() - fixed) / stretchSize; 163 else // reuse stretchX, but keep it negative as a signal 164 stretchX = SkScalarDiv(-bounds.width(), fixed); 165 } 166 167 if (numYStretch > 0) { 168 int stretchSize = 0; 169 for (int i = 1; i < numYDivs; i += 2) { 170 stretchSize += yDivs[i] - yDivs[i-1]; 171 } 172 const SkScalar fixed = SkIntToScalar(bitmap.height() - stretchSize); 173 if (bounds.height() >= fixed) 174 stretchY = (bounds.height() - fixed) / stretchSize; 175 else // reuse stretchX, but keep it negative as a signal 176 stretchY = SkScalarDiv(-bounds.height(), fixed); 177 } 178 179#if 0 180 SkDebugf("---- drawasamesh [%d %d] -> [%g %g] <%d %d> (%g %g)\n", 181 bitmap.width(), bitmap.height(), 182 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()), 183 numXDivs + 1, numYDivs + 1, 184 SkScalarToFloat(stretchX), SkScalarToFloat(stretchY)); 185#endif 186 187 const int vCount = (numXDivs + 2) * (numYDivs + 2); 188 // number of celss * 2 (tris per cell) * 3 (verts per tri) 189 const int indexCount = (numXDivs + 1) * (numYDivs + 1) * 2 * 3; 190 // allocate 2 times, one for verts, one for texs, plus indices 191 SkAutoMalloc storage(vCount * sizeof(SkPoint) * 2 + 192 indexCount * sizeof(uint16_t)); 193 SkPoint* verts = (SkPoint*)storage.get(); 194 SkPoint* texs = verts + vCount; 195 uint16_t* indices = (uint16_t*)(texs + vCount); 196 197 mesh.fVerts = verts; 198 mesh.fTexs = texs; 199 mesh.fColors = NULL; 200 mesh.fIndices = NULL; 201 202 // we use <= for YDivs, since the prebuild indices work for 3x2 and 3x1 too 203 if (numXDivs == 2 && numYDivs <= 2) { 204 mesh.fIndices = g3x3Indices; 205 } else { 206 SkDEBUGCODE(int n =) fillIndices(indices, numXDivs + 1, numYDivs + 1); 207 SkASSERT(n == indexCount); 208 mesh.fIndices = indices; 209 } 210 211 SkScalar vy = bounds.fTop; 212 fillRow(verts, texs, vy, 0, bounds, xDivs, numXDivs, 213 stretchX, bitmap.width()); 214 verts += numXDivs + 2; 215 texs += numXDivs + 2; 216 for (int y = 0; y < numYDivs; y++) { 217 const SkScalar ty = SkIntToScalar(yDivs[y]); 218 if (stretchY >= 0) { 219 if (y & 1) { 220 vy += stretchY; 221 } else { 222 vy += ty; 223 } 224 } else { // shrink fixed sections, and collaps stretchy sections 225 if (y & 1) { 226 ;// do nothing 227 } else { 228 vy += SkScalarMul(ty, -stretchY); 229 } 230 } 231 fillRow(verts, texs, vy, ty, bounds, xDivs, numXDivs, 232 stretchX, bitmap.width()); 233 verts += numXDivs + 2; 234 texs += numXDivs + 2; 235 } 236 fillRow(verts, texs, bounds.fBottom, SkIntToScalar(bitmap.height()), 237 bounds, xDivs, numXDivs, stretchX, bitmap.width()); 238 239 SkShader* shader = SkShader::CreateBitmapShader(bitmap, 240 SkShader::kClamp_TileMode, 241 SkShader::kClamp_TileMode); 242 SkPaint p; 243 if (paint) { 244 p = *paint; 245 } 246 p.setShader(shader)->unref(); 247 canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vCount, 248 mesh.fVerts, mesh.fTexs, mesh.fColors, NULL, 249 mesh.fIndices, indexCount, p); 250} 251 252/////////////////////////////////////////////////////////////////////////////// 253 254static void drawNineViaRects(SkCanvas* canvas, const SkRect& dst, 255 const SkBitmap& bitmap, const SkIRect& margins, 256 const SkPaint* paint) { 257 const int32_t srcX[4] = { 258 0, margins.fLeft, bitmap.width() - margins.fRight, bitmap.width() 259 }; 260 const int32_t srcY[4] = { 261 0, margins.fTop, bitmap.height() - margins.fBottom, bitmap.height() 262 }; 263 SkScalar dstX[4] = { 264 dst.fLeft, dst.fLeft + SkIntToScalar(margins.fLeft), 265 dst.fRight - SkIntToScalar(margins.fRight), dst.fRight 266 }; 267 SkScalar dstY[4] = { 268 dst.fTop, dst.fTop + SkIntToScalar(margins.fTop), 269 dst.fBottom - SkIntToScalar(margins.fBottom), dst.fBottom 270 }; 271 272 if (dstX[1] > dstX[2]) { 273 dstX[1] = dstX[0] + (dstX[3] - dstX[0]) * SkIntToScalar(margins.fLeft) / 274 (SkIntToScalar(margins.fLeft) + SkIntToScalar(margins.fRight)); 275 dstX[2] = dstX[1]; 276 } 277 278 if (dstY[1] > dstY[2]) { 279 dstY[1] = dstY[0] + (dstY[3] - dstY[0]) * SkIntToScalar(margins.fTop) / 280 (SkIntToScalar(margins.fTop) + SkIntToScalar(margins.fBottom)); 281 dstY[2] = dstY[1]; 282 } 283 284 SkIRect s; 285 SkRect d; 286 for (int y = 0; y < 3; y++) { 287 s.fTop = srcY[y]; 288 s.fBottom = srcY[y+1]; 289 d.fTop = dstY[y]; 290 d.fBottom = dstY[y+1]; 291 for (int x = 0; x < 3; x++) { 292 s.fLeft = srcX[x]; 293 s.fRight = srcX[x+1]; 294 d.fLeft = dstX[x]; 295 d.fRight = dstX[x+1]; 296 canvas->drawBitmapRect(bitmap, &s, d, paint); 297 } 298 } 299} 300 301void SkNinePatch::DrawNine(SkCanvas* canvas, const SkRect& bounds, 302 const SkBitmap& bitmap, const SkIRect& margins, 303 const SkPaint* paint) { 304 /** Our vertices code has numerical precision problems if the transformed 305 coordinates land directly on a 1/2 pixel boundary. To work around that 306 for now, we only take the vertices case if we are in opengl. Also, 307 when not in GL, the vertices impl is slower (more math) than calling 308 the viaRects code. 309 */ 310 if (false /* is our canvas backed by a gpu?*/) { 311 int32_t xDivs[2]; 312 int32_t yDivs[2]; 313 314 xDivs[0] = margins.fLeft; 315 xDivs[1] = bitmap.width() - margins.fRight; 316 yDivs[0] = margins.fTop; 317 yDivs[1] = bitmap.height() - margins.fBottom; 318 319 if (xDivs[0] > xDivs[1]) { 320 xDivs[0] = bitmap.width() * margins.fLeft / 321 (margins.fLeft + margins.fRight); 322 xDivs[1] = xDivs[0]; 323 } 324 if (yDivs[0] > yDivs[1]) { 325 yDivs[0] = bitmap.height() * margins.fTop / 326 (margins.fTop + margins.fBottom); 327 yDivs[1] = yDivs[0]; 328 } 329 330 SkNinePatch::DrawMesh(canvas, bounds, bitmap, 331 xDivs, 2, yDivs, 2, paint); 332 } else { 333 drawNineViaRects(canvas, bounds, bitmap, margins, paint); 334 } 335} 336