blob: f82053c32d1dff9e95fcaff3aa478eec3469c5c0 [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"
19
20#include <utils/ResourceTypes.h>
21
22#include "SkBitmap.h"
23#include "SkCanvas.h"
24#include "SkNinePatch.h"
25#include "SkPaint.h"
26#include "SkUnPreMultiply.h"
27
28#define USE_TRACEx
29
30#ifdef USE_TRACE
31 static bool gTrace;
32#endif
33
34#include "SkColorPriv.h"
35
36#include <utils/Log.h>
37
38static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) {
39 switch (bitmap.getConfig()) {
40 case SkBitmap::kARGB_8888_Config:
41 *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y));
42 break;
43 case SkBitmap::kRGB_565_Config:
44 *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y));
45 break;
46 case SkBitmap::kARGB_4444_Config:
47 *c = SkUnPreMultiply::PMColorToColor(
48 SkPixel4444ToPixel32(*bitmap.getAddr16(x, y)));
49 break;
50 case SkBitmap::kIndex8_Config: {
51 SkColorTable* ctable = bitmap.getColorTable();
52 *c = SkUnPreMultiply::PMColorToColor(
53 (*ctable)[*bitmap.getAddr8(x, y)]);
54 break;
55 }
56 default:
57 return false;
58 }
59 return true;
60}
61
62static SkColor modAlpha(SkColor c, int alpha) {
63 int scale = alpha + (alpha >> 7);
64 int a = SkColorGetA(c) * scale >> 8;
65 return SkColorSetA(c, a);
66}
67
68static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst,
69 const SkBitmap& bitmap, const SkPaint& paint,
70 SkColor initColor, uint32_t colorHint,
71 bool hasXfer) {
72 if (colorHint != android::Res_png_9patch::NO_COLOR) {
73 ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha()));
74 canvas->drawRect(dst, paint);
75 ((SkPaint*)&paint)->setColor(initColor);
76 } else if (src.width() == 1 && src.height() == 1) {
77 SkColor c;
78 if (!getColor(bitmap, src.fLeft, src.fTop, &c)) {
79 goto SLOW_CASE;
80 }
81 if (0 != c || hasXfer) {
82 SkColor prev = paint.getColor();
83 ((SkPaint*)&paint)->setColor(c);
84 canvas->drawRect(dst, paint);
85 ((SkPaint*)&paint)->setColor(prev);
86 }
87 } else {
88 SLOW_CASE:
89 canvas->drawBitmapRect(bitmap, &src, dst, &paint);
90 }
91}
92
93SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint,
94 int srcSpace, int numStrechyPixelsRemaining,
95 int numFixedPixelsRemaining) {
96 SkScalar spaceRemaining = boundsLimit - startingPoint;
97 SkScalar stretchySpaceRemaining =
98 spaceRemaining - SkIntToScalar(numFixedPixelsRemaining);
99 return SkScalarMulDiv(srcSpace, stretchySpaceRemaining,
100 numStrechyPixelsRemaining);
101}
102
103void NinePatch_Draw(SkCanvas* canvas, const SkRect& bounds,
104 const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
105 const SkPaint* paint, SkRegion** outRegion) {
106 if (canvas && canvas->quickReject(bounds, SkCanvas::kBW_EdgeType)) {
107 return;
108 }
109
110 // if our canvas is GL, draw this as a mesh, which will be faster than
111 // in parts (which is faster for raster)
112 if (canvas && canvas->getViewport(NULL)) {
113 SkNinePatch::DrawMesh(canvas, bounds, bitmap,
114 chunk.xDivs, chunk.numXDivs,
115 chunk.yDivs, chunk.numYDivs,
116 paint);
117 return;
118 }
119
120#ifdef USE_TRACE
121 gTrace = true;
122#endif
123
124 SkASSERT(canvas || outRegion);
125
126#if 0
127 if (canvas) {
128 const SkMatrix& m = canvas->getTotalMatrix();
129 SkDebugf("ninepatch [%g %g %g] [%g %g %g]\n",
130 SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]),
131 SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5]));
132 }
133#endif
134
135#ifdef USE_TRACE
136 if (gTrace) {
137 SkDEBUGF(("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height())));
138 SkDEBUGF(("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height()));
139 SkDEBUGF(("======== ninepatch xDivs [%d,%d]\n", chunk.xDivs[0], chunk.xDivs[1]));
140 SkDEBUGF(("======== ninepatch yDivs [%d,%d]\n", chunk.yDivs[0], chunk.yDivs[1]));
141 }
142#endif
143
144 if (bounds.isEmpty() ||
145 bitmap.width() == 0 || bitmap.height() == 0 ||
146 (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
147 {
148#ifdef USE_TRACE
149 if (gTrace) SkDEBUGF(("======== abort ninepatch draw\n"));
150#endif
151 return;
152 }
153
154 // should try a quick-reject test before calling lockPixels
155
156 SkAutoLockPixels alp(bitmap);
157 // after the lock, it is valid to check getPixels()
158 if (bitmap.getPixels() == NULL)
159 return;
160
161 SkPaint defaultPaint;
162 if (NULL == paint) {
163 paint = &defaultPaint;
164 }
165
166 const bool hasXfer = paint->getXfermode() != NULL;
167 SkRect dst;
168 SkIRect src;
169
170 const int32_t x0 = chunk.xDivs[0];
171 const int32_t y0 = chunk.yDivs[0];
172 const SkColor initColor = ((SkPaint*)paint)->getColor();
173 const uint8_t numXDivs = chunk.numXDivs;
174 const uint8_t numYDivs = chunk.numYDivs;
175 int i;
176 int j;
177 int colorIndex = 0;
178 uint32_t color;
179 bool xIsStretchable;
180 const bool initialXIsStretchable = (x0 == 0);
181 bool yIsStretchable = (y0 == 0);
182 const int bitmapWidth = bitmap.width();
183 const int bitmapHeight = bitmap.height();
184
185 SkScalar* dstRights = (SkScalar*) alloca((numXDivs + 1) * sizeof(SkScalar));
186 bool dstRightsHaveBeenCached = false;
187
188 int numStretchyXPixelsRemaining = 0;
189 for (i = 0; i < numXDivs; i += 2) {
190 numStretchyXPixelsRemaining += chunk.xDivs[i + 1] - chunk.xDivs[i];
191 }
192 int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
193 int numStretchyYPixelsRemaining = 0;
194 for (i = 0; i < numYDivs; i += 2) {
195 numStretchyYPixelsRemaining += chunk.yDivs[i + 1] - chunk.yDivs[i];
196 }
197 int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
198
199#if 0
200 SkDebugf("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
201 bitmap.width(), bitmap.height(),
202 SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
203 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
204 numXDivs, numYDivs);
205#endif
206
207 src.fTop = 0;
208 dst.fTop = bounds.fTop;
209 // The first row always starts with the top being at y=0 and the bottom
210 // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
211 // the first row is stretchable along the Y axis, otherwise it is fixed.
212 // The last row always ends with the bottom being bitmap.height and the top
213 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
214 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
215 // the Y axis, otherwise it is fixed.
216 //
217 // The first and last columns are similarly treated with respect to the X
218 // axis.
219 //
220 // The above is to help explain some of the special casing that goes on the
221 // code below.
222
223 // The initial yDiv and whether the first row is considered stretchable or
224 // not depends on whether yDiv[0] was zero or not.
225 for (j = yIsStretchable ? 1 : 0;
226 j <= numYDivs && src.fTop < bitmapHeight;
227 j++, yIsStretchable = !yIsStretchable) {
228 src.fLeft = 0;
229 dst.fLeft = bounds.fLeft;
230 if (j == numYDivs) {
231 src.fBottom = bitmapHeight;
232 dst.fBottom = bounds.fBottom;
233 } else {
234 src.fBottom = chunk.yDivs[j];
235 const int srcYSize = src.fBottom - src.fTop;
236 if (yIsStretchable) {
237 dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop,
238 srcYSize,
239 numStretchyYPixelsRemaining,
240 numFixedYPixelsRemaining);
241 numStretchyYPixelsRemaining -= srcYSize;
242 } else {
243 dst.fBottom = dst.fTop + SkIntToScalar(srcYSize);
244 numFixedYPixelsRemaining -= srcYSize;
245 }
246 }
247
248 xIsStretchable = initialXIsStretchable;
249 // The initial xDiv and whether the first column is considered
250 // stretchable or not depends on whether xDiv[0] was zero or not.
251 for (i = xIsStretchable ? 1 : 0;
252 i <= numXDivs && src.fLeft < bitmapWidth;
253 i++, xIsStretchable = !xIsStretchable) {
254 color = chunk.colors[colorIndex++];
255 if (i == numXDivs) {
256 src.fRight = bitmapWidth;
257 dst.fRight = bounds.fRight;
258 } else {
259 src.fRight = chunk.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 //LOGI("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 0
304 SkDebugf("-- 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 SkDebugf("--- skip patch\n");
310 }
311#endif
312 drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
313 color, hasXfer);
314 }
315
316nextDiv:
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}