michael@0: michael@0: /* michael@0: * Copyright 2006 The Android Open Source Project michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: #include "SkNinePatch.h" michael@0: #include "SkCanvas.h" michael@0: #include "SkShader.h" michael@0: michael@0: static const uint16_t g3x3Indices[] = { michael@0: 0, 5, 1, 0, 4, 5, michael@0: 1, 6, 2, 1, 5, 6, michael@0: 2, 7, 3, 2, 6, 7, michael@0: michael@0: 4, 9, 5, 4, 8, 9, michael@0: 5, 10, 6, 5, 9, 10, michael@0: 6, 11, 7, 6, 10, 11, michael@0: michael@0: 8, 13, 9, 8, 12, 13, michael@0: 9, 14, 10, 9, 13, 14, michael@0: 10, 15, 11, 10, 14, 15 michael@0: }; michael@0: michael@0: static int fillIndices(uint16_t indices[], int xCount, int yCount) { michael@0: uint16_t* startIndices = indices; michael@0: michael@0: int n = 0; michael@0: for (int y = 0; y < yCount; y++) { michael@0: for (int x = 0; x < xCount; x++) { michael@0: *indices++ = n; michael@0: *indices++ = n + xCount + 2; michael@0: *indices++ = n + 1; michael@0: michael@0: *indices++ = n; michael@0: *indices++ = n + xCount + 1; michael@0: *indices++ = n + xCount + 2; michael@0: michael@0: n += 1; michael@0: } michael@0: n += 1; michael@0: } michael@0: return static_cast(indices - startIndices); michael@0: } michael@0: michael@0: // Computes the delta between vertices along a single axis michael@0: static SkScalar computeVertexDelta(bool isStretchyVertex, michael@0: SkScalar currentVertex, michael@0: SkScalar prevVertex, michael@0: SkScalar stretchFactor) { michael@0: // the standard delta between vertices if no stretching is required michael@0: SkScalar delta = currentVertex - prevVertex; michael@0: michael@0: // if the stretch factor is negative or zero we need to shrink the 9-patch michael@0: // to fit within the target bounds. This means that we will eliminate all michael@0: // stretchy areas and scale the fixed areas to fit within the target bounds. michael@0: if (stretchFactor <= 0) { michael@0: if (isStretchyVertex) michael@0: delta = 0; // collapse stretchable areas michael@0: else michael@0: delta = SkScalarMul(delta, -stretchFactor); // scale fixed areas michael@0: // if the stretch factor is positive then we use the standard delta for michael@0: // fixed and scale the stretchable areas to fill the target bounds. michael@0: } else if (isStretchyVertex) { michael@0: delta = SkScalarMul(delta, stretchFactor); michael@0: } michael@0: michael@0: return delta; michael@0: } michael@0: michael@0: static void fillRow(SkPoint verts[], SkPoint texs[], michael@0: const SkScalar vy, const SkScalar ty, michael@0: const SkRect& bounds, const int32_t xDivs[], int numXDivs, michael@0: const SkScalar stretchX, int width) { michael@0: SkScalar vx = bounds.fLeft; michael@0: verts->set(vx, vy); verts++; michael@0: texs->set(0, ty); texs++; michael@0: michael@0: SkScalar prev = 0; michael@0: for (int x = 0; x < numXDivs; x++) { michael@0: michael@0: const SkScalar tx = SkIntToScalar(xDivs[x]); michael@0: vx += computeVertexDelta(x & 1, tx, prev, stretchX); michael@0: prev = tx; michael@0: michael@0: verts->set(vx, vy); verts++; michael@0: texs->set(tx, ty); texs++; michael@0: } michael@0: verts->set(bounds.fRight, vy); verts++; michael@0: texs->set(SkIntToScalar(width), ty); texs++; michael@0: } michael@0: michael@0: struct Mesh { michael@0: const SkPoint* fVerts; michael@0: const SkPoint* fTexs; michael@0: const SkColor* fColors; michael@0: const uint16_t* fIndices; michael@0: }; michael@0: michael@0: void SkNinePatch::DrawMesh(SkCanvas* canvas, const SkRect& bounds, michael@0: const SkBitmap& bitmap, michael@0: const int32_t xDivs[], int numXDivs, michael@0: const int32_t yDivs[], int numYDivs, michael@0: const SkPaint* paint) { michael@0: if (bounds.isEmpty() || bitmap.width() == 0 || bitmap.height() == 0) { michael@0: return; michael@0: } michael@0: michael@0: // should try a quick-reject test before calling lockPixels michael@0: SkAutoLockPixels alp(bitmap); michael@0: // after the lock, it is valid to check michael@0: if (!bitmap.readyToDraw()) { michael@0: return; michael@0: } michael@0: michael@0: // check for degenerate divs (just an optimization, not required) michael@0: { michael@0: int i; michael@0: int zeros = 0; michael@0: for (i = 0; i < numYDivs && yDivs[i] == 0; i++) { michael@0: zeros += 1; michael@0: } michael@0: numYDivs -= zeros; michael@0: yDivs += zeros; michael@0: for (i = numYDivs - 1; i >= 0 && yDivs[i] == bitmap.height(); --i) { michael@0: numYDivs -= 1; michael@0: } michael@0: } michael@0: michael@0: Mesh mesh; michael@0: michael@0: const int numXStretch = (numXDivs + 1) >> 1; michael@0: const int numYStretch = (numYDivs + 1) >> 1; michael@0: michael@0: if (numXStretch < 1 && numYStretch < 1) { michael@0: canvas->drawBitmapRect(bitmap, NULL, bounds, paint); michael@0: return; michael@0: } michael@0: michael@0: if (false) { michael@0: int i; michael@0: for (i = 0; i < numXDivs; i++) { michael@0: SkDebugf("--- xdivs[%d] %d\n", i, xDivs[i]); michael@0: } michael@0: for (i = 0; i < numYDivs; i++) { michael@0: SkDebugf("--- ydivs[%d] %d\n", i, yDivs[i]); michael@0: } michael@0: } michael@0: michael@0: SkScalar stretchX = 0, stretchY = 0; michael@0: michael@0: if (numXStretch > 0) { michael@0: int stretchSize = 0; michael@0: for (int i = 1; i < numXDivs; i += 2) { michael@0: stretchSize += xDivs[i] - xDivs[i-1]; michael@0: } michael@0: const SkScalar fixed = SkIntToScalar(bitmap.width() - stretchSize); michael@0: if (bounds.width() >= fixed) michael@0: stretchX = (bounds.width() - fixed) / stretchSize; michael@0: else // reuse stretchX, but keep it negative as a signal michael@0: stretchX = SkScalarDiv(-bounds.width(), fixed); michael@0: } michael@0: michael@0: if (numYStretch > 0) { michael@0: int stretchSize = 0; michael@0: for (int i = 1; i < numYDivs; i += 2) { michael@0: stretchSize += yDivs[i] - yDivs[i-1]; michael@0: } michael@0: const SkScalar fixed = SkIntToScalar(bitmap.height() - stretchSize); michael@0: if (bounds.height() >= fixed) michael@0: stretchY = (bounds.height() - fixed) / stretchSize; michael@0: else // reuse stretchX, but keep it negative as a signal michael@0: stretchY = SkScalarDiv(-bounds.height(), fixed); michael@0: } michael@0: michael@0: #if 0 michael@0: SkDebugf("---- drawasamesh [%d %d] -> [%g %g] <%d %d> (%g %g)\n", michael@0: bitmap.width(), bitmap.height(), michael@0: SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()), michael@0: numXDivs + 1, numYDivs + 1, michael@0: SkScalarToFloat(stretchX), SkScalarToFloat(stretchY)); michael@0: #endif michael@0: michael@0: const int vCount = (numXDivs + 2) * (numYDivs + 2); michael@0: // number of celss * 2 (tris per cell) * 3 (verts per tri) michael@0: const int indexCount = (numXDivs + 1) * (numYDivs + 1) * 2 * 3; michael@0: // allocate 2 times, one for verts, one for texs, plus indices michael@0: SkAutoMalloc storage(vCount * sizeof(SkPoint) * 2 + michael@0: indexCount * sizeof(uint16_t)); michael@0: SkPoint* verts = (SkPoint*)storage.get(); michael@0: SkPoint* texs = verts + vCount; michael@0: uint16_t* indices = (uint16_t*)(texs + vCount); michael@0: michael@0: mesh.fVerts = verts; michael@0: mesh.fTexs = texs; michael@0: mesh.fColors = NULL; michael@0: mesh.fIndices = NULL; michael@0: michael@0: // we use <= for YDivs, since the prebuild indices work for 3x2 and 3x1 too michael@0: if (numXDivs == 2 && numYDivs <= 2) { michael@0: mesh.fIndices = g3x3Indices; michael@0: } else { michael@0: SkDEBUGCODE(int n =) fillIndices(indices, numXDivs + 1, numYDivs + 1); michael@0: SkASSERT(n == indexCount); michael@0: mesh.fIndices = indices; michael@0: } michael@0: michael@0: SkScalar vy = bounds.fTop; michael@0: fillRow(verts, texs, vy, 0, bounds, xDivs, numXDivs, michael@0: stretchX, bitmap.width()); michael@0: verts += numXDivs + 2; michael@0: texs += numXDivs + 2; michael@0: for (int y = 0; y < numYDivs; y++) { michael@0: const SkScalar ty = SkIntToScalar(yDivs[y]); michael@0: if (stretchY >= 0) { michael@0: if (y & 1) { michael@0: vy += stretchY; michael@0: } else { michael@0: vy += ty; michael@0: } michael@0: } else { // shrink fixed sections, and collaps stretchy sections michael@0: if (y & 1) { michael@0: ;// do nothing michael@0: } else { michael@0: vy += SkScalarMul(ty, -stretchY); michael@0: } michael@0: } michael@0: fillRow(verts, texs, vy, ty, bounds, xDivs, numXDivs, michael@0: stretchX, bitmap.width()); michael@0: verts += numXDivs + 2; michael@0: texs += numXDivs + 2; michael@0: } michael@0: fillRow(verts, texs, bounds.fBottom, SkIntToScalar(bitmap.height()), michael@0: bounds, xDivs, numXDivs, stretchX, bitmap.width()); michael@0: michael@0: SkShader* shader = SkShader::CreateBitmapShader(bitmap, michael@0: SkShader::kClamp_TileMode, michael@0: SkShader::kClamp_TileMode); michael@0: SkPaint p; michael@0: if (paint) { michael@0: p = *paint; michael@0: } michael@0: p.setShader(shader)->unref(); michael@0: canvas->drawVertices(SkCanvas::kTriangles_VertexMode, vCount, michael@0: mesh.fVerts, mesh.fTexs, mesh.fColors, NULL, michael@0: mesh.fIndices, indexCount, p); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: static void drawNineViaRects(SkCanvas* canvas, const SkRect& dst, michael@0: const SkBitmap& bitmap, const SkIRect& margins, michael@0: const SkPaint* paint) { michael@0: const int32_t srcX[4] = { michael@0: 0, margins.fLeft, bitmap.width() - margins.fRight, bitmap.width() michael@0: }; michael@0: const int32_t srcY[4] = { michael@0: 0, margins.fTop, bitmap.height() - margins.fBottom, bitmap.height() michael@0: }; michael@0: SkScalar dstX[4] = { michael@0: dst.fLeft, dst.fLeft + SkIntToScalar(margins.fLeft), michael@0: dst.fRight - SkIntToScalar(margins.fRight), dst.fRight michael@0: }; michael@0: SkScalar dstY[4] = { michael@0: dst.fTop, dst.fTop + SkIntToScalar(margins.fTop), michael@0: dst.fBottom - SkIntToScalar(margins.fBottom), dst.fBottom michael@0: }; michael@0: michael@0: if (dstX[1] > dstX[2]) { michael@0: dstX[1] = dstX[0] + (dstX[3] - dstX[0]) * SkIntToScalar(margins.fLeft) / michael@0: (SkIntToScalar(margins.fLeft) + SkIntToScalar(margins.fRight)); michael@0: dstX[2] = dstX[1]; michael@0: } michael@0: michael@0: if (dstY[1] > dstY[2]) { michael@0: dstY[1] = dstY[0] + (dstY[3] - dstY[0]) * SkIntToScalar(margins.fTop) / michael@0: (SkIntToScalar(margins.fTop) + SkIntToScalar(margins.fBottom)); michael@0: dstY[2] = dstY[1]; michael@0: } michael@0: michael@0: SkIRect s; michael@0: SkRect d; michael@0: for (int y = 0; y < 3; y++) { michael@0: s.fTop = srcY[y]; michael@0: s.fBottom = srcY[y+1]; michael@0: d.fTop = dstY[y]; michael@0: d.fBottom = dstY[y+1]; michael@0: for (int x = 0; x < 3; x++) { michael@0: s.fLeft = srcX[x]; michael@0: s.fRight = srcX[x+1]; michael@0: d.fLeft = dstX[x]; michael@0: d.fRight = dstX[x+1]; michael@0: canvas->drawBitmapRect(bitmap, &s, d, paint); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void SkNinePatch::DrawNine(SkCanvas* canvas, const SkRect& bounds, michael@0: const SkBitmap& bitmap, const SkIRect& margins, michael@0: const SkPaint* paint) { michael@0: /** Our vertices code has numerical precision problems if the transformed michael@0: coordinates land directly on a 1/2 pixel boundary. To work around that michael@0: for now, we only take the vertices case if we are in opengl. Also, michael@0: when not in GL, the vertices impl is slower (more math) than calling michael@0: the viaRects code. michael@0: */ michael@0: if (false /* is our canvas backed by a gpu?*/) { michael@0: int32_t xDivs[2]; michael@0: int32_t yDivs[2]; michael@0: michael@0: xDivs[0] = margins.fLeft; michael@0: xDivs[1] = bitmap.width() - margins.fRight; michael@0: yDivs[0] = margins.fTop; michael@0: yDivs[1] = bitmap.height() - margins.fBottom; michael@0: michael@0: if (xDivs[0] > xDivs[1]) { michael@0: xDivs[0] = bitmap.width() * margins.fLeft / michael@0: (margins.fLeft + margins.fRight); michael@0: xDivs[1] = xDivs[0]; michael@0: } michael@0: if (yDivs[0] > yDivs[1]) { michael@0: yDivs[0] = bitmap.height() * margins.fTop / michael@0: (margins.fTop + margins.fBottom); michael@0: yDivs[1] = yDivs[0]; michael@0: } michael@0: michael@0: SkNinePatch::DrawMesh(canvas, bounds, bitmap, michael@0: xDivs, 2, yDivs, 2, paint); michael@0: } else { michael@0: drawNineViaRects(canvas, bounds, bitmap, margins, paint); michael@0: } michael@0: }