blob: 7aaca8a12ae573f80de1f24affab4e2b38091247 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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"
Dianne Hackborn8cae1242009-09-10 14:32:16 -070019#define LOG_NDEBUG 1
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020
21#include <utils/ResourceTypes.h>
Dianne Hackborn8cae1242009-09-10 14:32:16 -070022#include <utils/Log.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023
24#include "SkBitmap.h"
25#include "SkCanvas.h"
26#include "SkNinePatch.h"
27#include "SkPaint.h"
28#include "SkUnPreMultiply.h"
29
Dianne Hackborn8cae1242009-09-10 14:32:16 -070030#define USE_TRACE
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031
32#ifdef USE_TRACE
33 static bool gTrace;
34#endif
35
36#include "SkColorPriv.h"
37
38#include <utils/Log.h>
39
40static 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
64static 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
70static 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
95SkScalar 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
105void 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, SkCanvas::kBW_EdgeType)) {
109 return;
110 }
Mike Reed211db4a2009-09-11 09:36:35 -0400111
112 SkPaint defaultPaint;
113 if (NULL == paint) {
114 // matches default dither in NinePatchDrawable.java.
115 defaultPaint.setDither(true);
116 paint = &defaultPaint;
117 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118
Derek Sollenbergerd39d1af2011-05-16 13:09:42 -0400119 // 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) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 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
Dianne Hackborn8cae1242009-09-10 14:32:16 -0700135#ifdef USE_TRACE
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 if (canvas) {
137 const SkMatrix& m = canvas->getTotalMatrix();
Steve Block71f2cf12011-10-20 11:56:00 +0100138 ALOGV("ninepatch [%g %g %g] [%g %g %g]\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 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) {
Steve Block71f2cf12011-10-20 11:56:00 +0100146 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]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150 }
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
Steve Block71f2cf12011-10-20 11:56:00 +0100158 if (gTrace) ALOGV("======== abort ninepatch draw\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159#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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 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
Dianne Hackborn8cae1242009-09-10 14:32:16 -0700203#ifdef USE_TRACE
Steve Block71f2cf12011-10-20 11:56:00 +0100204 ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 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 //LOGI("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) {
Dianne Hackborn8cae1242009-09-10 14:32:16 -0700307#ifdef USE_TRACE
Steve Block71f2cf12011-10-20 11:56:00 +0100308 ALOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 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()) {
Steve Block71f2cf12011-10-20 11:56:00 +0100313 ALOGV("--- skip patch\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314 }
315#endif
316 drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
317 color, hasXfer);
318 }
319
320nextDiv:
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}