Home | History | Annotate | Download | only in utils
      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 
     14 static 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 
     28 static 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 indices - startIndices;
     47 }
     48 
     49 // Computes the delta between vertices along a single axis
     50 static 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 
     74 static 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 
     96 struct Mesh {
     97     const SkPoint*  fVerts;
     98     const SkPoint*  fTexs;
     99     const SkColor*  fColors;
    100     const uint16_t* fIndices;
    101 };
    102 
    103 void 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 
    254 static 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 
    301 void 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