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