Home | History | Annotate | Download | only in graphics
      1 /*
      2 **
      3 ** Copyright 2006, The Android Open Source Project
      4 **
      5 ** Licensed under the Apache License, Version 2.0 (the "License");
      6 ** you may not use this file except in compliance with the License.
      7 ** You may obtain a copy of the License at
      8 **
      9 **     http://www.apache.org/licenses/LICENSE-2.0
     10 **
     11 ** Unless required by applicable law or agreed to in writing, software
     12 ** distributed under the License is distributed on an "AS IS" BASIS,
     13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 ** See the License for the specific language governing permissions and
     15 ** limitations under the License.
     16 */
     17 
     18 #define LOG_TAG "NinePatch"
     19 #define LOG_NDEBUG 1
     20 
     21 #include <androidfw/ResourceTypes.h>
     22 #include <utils/Log.h>
     23 
     24 #include "SkBitmap.h"
     25 #include "SkCanvas.h"
     26 #include "SkNinePatch.h"
     27 #include "SkPaint.h"
     28 #include "SkUnPreMultiply.h"
     29 
     30 #define USE_TRACE
     31 
     32 #ifdef USE_TRACE
     33     static bool gTrace;
     34 #endif
     35 
     36 #include "SkColorPriv.h"
     37 
     38 #include <utils/Log.h>
     39 
     40 static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) {
     41     switch (bitmap.getConfig()) {
     42         case SkBitmap::kARGB_8888_Config:
     43             *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y));
     44             break;
     45         case SkBitmap::kRGB_565_Config:
     46             *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y));
     47             break;
     48         case SkBitmap::kARGB_4444_Config:
     49             *c = SkUnPreMultiply::PMColorToColor(
     50                                 SkPixel4444ToPixel32(*bitmap.getAddr16(x, y)));
     51             break;
     52         case SkBitmap::kIndex8_Config: {
     53             SkColorTable* ctable = bitmap.getColorTable();
     54             *c = SkUnPreMultiply::PMColorToColor(
     55                                             (*ctable)[*bitmap.getAddr8(x, y)]);
     56             break;
     57         }
     58         default:
     59             return false;
     60     }
     61     return true;
     62 }
     63 
     64 static SkColor modAlpha(SkColor c, int alpha) {
     65     int scale = alpha + (alpha >> 7);
     66     int a = SkColorGetA(c) * scale >> 8;
     67     return SkColorSetA(c, a);
     68 }
     69 
     70 static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst,
     71                               const SkBitmap& bitmap, const SkPaint& paint,
     72                               SkColor initColor, uint32_t colorHint,
     73                               bool hasXfer) {
     74     if (colorHint !=  android::Res_png_9patch::NO_COLOR) {
     75         ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha()));
     76         canvas->drawRect(dst, paint);
     77         ((SkPaint*)&paint)->setColor(initColor);
     78     } else if (src.width() == 1 && src.height() == 1) {
     79         SkColor c;
     80         if (!getColor(bitmap, src.fLeft, src.fTop, &c)) {
     81             goto SLOW_CASE;
     82         }
     83         if (0 != c || hasXfer) {
     84             SkColor prev = paint.getColor();
     85             ((SkPaint*)&paint)->setColor(c);
     86             canvas->drawRect(dst, paint);
     87             ((SkPaint*)&paint)->setColor(prev);
     88         }
     89     } else {
     90     SLOW_CASE:
     91         canvas->drawBitmapRect(bitmap, &src, dst, &paint);
     92     }
     93 }
     94 
     95 SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint,
     96                           int srcSpace, int numStrechyPixelsRemaining,
     97                           int numFixedPixelsRemaining) {
     98     SkScalar spaceRemaining = boundsLimit - startingPoint;
     99     SkScalar stretchySpaceRemaining =
    100                 spaceRemaining - SkIntToScalar(numFixedPixelsRemaining);
    101     return SkScalarMulDiv(srcSpace, stretchySpaceRemaining,
    102                           numStrechyPixelsRemaining);
    103 }
    104 
    105 void NinePatch_Draw(SkCanvas* canvas, const SkRect& bounds,
    106                        const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
    107                        const SkPaint* paint, SkRegion** outRegion) {
    108     if (canvas && canvas->quickReject(bounds)) {
    109         return;
    110     }
    111 
    112     SkPaint defaultPaint;
    113     if (NULL == paint) {
    114         // matches default dither in NinePatchDrawable.java.
    115         defaultPaint.setDither(true);
    116         paint = &defaultPaint;
    117     }
    118 
    119     // if our SkCanvas were back by GL we should enable this and draw this as
    120     // a mesh, which will be faster in most cases.
    121     if (false) {
    122         SkNinePatch::DrawMesh(canvas, bounds, bitmap,
    123                               chunk.xDivs, chunk.numXDivs,
    124                               chunk.yDivs, chunk.numYDivs,
    125                               paint);
    126         return;
    127     }
    128 
    129 #ifdef USE_TRACE
    130     gTrace = true;
    131 #endif
    132 
    133     SkASSERT(canvas || outRegion);
    134 
    135 #ifdef USE_TRACE
    136     if (canvas) {
    137         const SkMatrix& m = canvas->getTotalMatrix();
    138         ALOGV("ninepatch [%g %g %g] [%g %g %g]\n",
    139                  SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]),
    140                  SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5]));
    141     }
    142 #endif
    143 
    144 #ifdef USE_TRACE
    145     if (gTrace) {
    146         ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()));
    147         ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height());
    148         ALOGV("======== ninepatch xDivs [%d,%d]\n", chunk.xDivs[0], chunk.xDivs[1]);
    149         ALOGV("======== ninepatch yDivs [%d,%d]\n", chunk.yDivs[0], chunk.yDivs[1]);
    150     }
    151 #endif
    152 
    153     if (bounds.isEmpty() ||
    154         bitmap.width() == 0 || bitmap.height() == 0 ||
    155         (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
    156     {
    157 #ifdef USE_TRACE
    158         if (gTrace) ALOGV("======== abort ninepatch draw\n");
    159 #endif
    160         return;
    161     }
    162 
    163     // should try a quick-reject test before calling lockPixels
    164 
    165     SkAutoLockPixels alp(bitmap);
    166     // after the lock, it is valid to check getPixels()
    167     if (bitmap.getPixels() == NULL)
    168         return;
    169 
    170     const bool hasXfer = paint->getXfermode() != NULL;
    171     SkRect      dst;
    172     SkIRect     src;
    173 
    174     const int32_t x0 = chunk.xDivs[0];
    175     const int32_t y0 = chunk.yDivs[0];
    176     const SkColor initColor = ((SkPaint*)paint)->getColor();
    177     const uint8_t numXDivs = chunk.numXDivs;
    178     const uint8_t numYDivs = chunk.numYDivs;
    179     int i;
    180     int j;
    181     int colorIndex = 0;
    182     uint32_t color;
    183     bool xIsStretchable;
    184     const bool initialXIsStretchable =  (x0 == 0);
    185     bool yIsStretchable = (y0 == 0);
    186     const int bitmapWidth = bitmap.width();
    187     const int bitmapHeight = bitmap.height();
    188 
    189     SkScalar* dstRights = (SkScalar*) alloca((numXDivs + 1) * sizeof(SkScalar));
    190     bool dstRightsHaveBeenCached = false;
    191 
    192     int numStretchyXPixelsRemaining = 0;
    193     for (i = 0; i < numXDivs; i += 2) {
    194         numStretchyXPixelsRemaining += chunk.xDivs[i + 1] - chunk.xDivs[i];
    195     }
    196     int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
    197     int numStretchyYPixelsRemaining = 0;
    198     for (i = 0; i < numYDivs; i += 2) {
    199         numStretchyYPixelsRemaining += chunk.yDivs[i + 1] - chunk.yDivs[i];
    200     }
    201     int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
    202 
    203 #ifdef USE_TRACE
    204     ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
    205              bitmap.width(), bitmap.height(),
    206              SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
    207              SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
    208              numXDivs, numYDivs);
    209 #endif
    210 
    211     src.fTop = 0;
    212     dst.fTop = bounds.fTop;
    213     // The first row always starts with the top being at y=0 and the bottom
    214     // being either yDivs[1] (if yDivs[0]=0) of yDivs[0].  In the former case
    215     // the first row is stretchable along the Y axis, otherwise it is fixed.
    216     // The last row always ends with the bottom being bitmap.height and the top
    217     // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
    218     // yDivs[numYDivs-1]. In the former case the last row is stretchable along
    219     // the Y axis, otherwise it is fixed.
    220     //
    221     // The first and last columns are similarly treated with respect to the X
    222     // axis.
    223     //
    224     // The above is to help explain some of the special casing that goes on the
    225     // code below.
    226 
    227     // The initial yDiv and whether the first row is considered stretchable or
    228     // not depends on whether yDiv[0] was zero or not.
    229     for (j = yIsStretchable ? 1 : 0;
    230           j <= numYDivs && src.fTop < bitmapHeight;
    231           j++, yIsStretchable = !yIsStretchable) {
    232         src.fLeft = 0;
    233         dst.fLeft = bounds.fLeft;
    234         if (j == numYDivs) {
    235             src.fBottom = bitmapHeight;
    236             dst.fBottom = bounds.fBottom;
    237         } else {
    238             src.fBottom = chunk.yDivs[j];
    239             const int srcYSize = src.fBottom - src.fTop;
    240             if (yIsStretchable) {
    241                 dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop,
    242                                                           srcYSize,
    243                                                           numStretchyYPixelsRemaining,
    244                                                           numFixedYPixelsRemaining);
    245                 numStretchyYPixelsRemaining -= srcYSize;
    246             } else {
    247                 dst.fBottom = dst.fTop + SkIntToScalar(srcYSize);
    248                 numFixedYPixelsRemaining -= srcYSize;
    249             }
    250         }
    251 
    252         xIsStretchable = initialXIsStretchable;
    253         // The initial xDiv and whether the first column is considered
    254         // stretchable or not depends on whether xDiv[0] was zero or not.
    255         for (i = xIsStretchable ? 1 : 0;
    256               i <= numXDivs && src.fLeft < bitmapWidth;
    257               i++, xIsStretchable = !xIsStretchable) {
    258             color = chunk.colors[colorIndex++];
    259             if (i == numXDivs) {
    260                 src.fRight = bitmapWidth;
    261                 dst.fRight = bounds.fRight;
    262             } else {
    263                 src.fRight = chunk.xDivs[i];
    264                 if (dstRightsHaveBeenCached) {
    265                     dst.fRight = dstRights[i];
    266                 } else {
    267                     const int srcXSize = src.fRight - src.fLeft;
    268                     if (xIsStretchable) {
    269                         dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft,
    270                                                                   srcXSize,
    271                                                                   numStretchyXPixelsRemaining,
    272                                                                   numFixedXPixelsRemaining);
    273                         numStretchyXPixelsRemaining -= srcXSize;
    274                     } else {
    275                         dst.fRight = dst.fLeft + SkIntToScalar(srcXSize);
    276                         numFixedXPixelsRemaining -= srcXSize;
    277                     }
    278                     dstRights[i] = dst.fRight;
    279                 }
    280             }
    281             // If this horizontal patch is too small to be displayed, leave
    282             // the destination left edge where it is and go on to the next patch
    283             // in the source.
    284             if (src.fLeft >= src.fRight) {
    285                 src.fLeft = src.fRight;
    286                 continue;
    287             }
    288             // Make sure that we actually have room to draw any bits
    289             if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) {
    290                 goto nextDiv;
    291             }
    292             // If this patch is transparent, skip and don't draw.
    293             if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) {
    294                 if (outRegion) {
    295                     if (*outRegion == NULL) {
    296                         *outRegion = new SkRegion();
    297                     }
    298                     SkIRect idst;
    299                     dst.round(&idst);
    300                     //ALOGI("Adding trans rect: (%d,%d)-(%d,%d)\n",
    301                     //     idst.fLeft, idst.fTop, idst.fRight, idst.fBottom);
    302                     (*outRegion)->op(idst, SkRegion::kUnion_Op);
    303                 }
    304                 goto nextDiv;
    305             }
    306             if (canvas) {
    307 #ifdef USE_TRACE
    308                 ALOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
    309                          src.fLeft, src.fTop, src.width(), src.height(),
    310                          SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop),
    311                          SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height()));
    312                 if (2 == src.width() && SkIntToScalar(5) == dst.width()) {
    313                     ALOGV("--- skip patch\n");
    314                 }
    315 #endif
    316                 drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
    317                                   color, hasXfer);
    318             }
    319 
    320 nextDiv:
    321             src.fLeft = src.fRight;
    322             dst.fLeft = dst.fRight;
    323         }
    324         src.fTop = src.fBottom;
    325         dst.fTop = dst.fBottom;
    326         dstRightsHaveBeenCached = true;
    327     }
    328 }
    329