blob: 2b59ca9266d168365d3d4e893203b1f2a25ca903 [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
Derek Sollenberger4c5efe92015-07-10 13:56:39 -040018#include "utils/NinePatch.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080019
20#include "SkBitmap.h"
21#include "SkCanvas.h"
Andreas Gampeed6b9df2014-11-20 22:02:20 -080022#include "SkColorPriv.h"
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023#include "SkNinePatch.h"
24#include "SkPaint.h"
25#include "SkUnPreMultiply.h"
26
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027#include <utils/Log.h>
28
Derek Sollenberger4c5efe92015-07-10 13:56:39 -040029namespace android {
30
Andreas Gampeed6b9df2014-11-20 22:02:20 -080031static const bool kUseTrace = true;
32static bool gTrace = false;
33
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) {
Mike Reed1103b322014-07-08 12:36:44 -040035 switch (bitmap.colorType()) {
36 case kN32_SkColorType:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037 *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y));
38 break;
Mike Reed1103b322014-07-08 12:36:44 -040039 case kRGB_565_SkColorType:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y));
41 break;
Mike Reed1103b322014-07-08 12:36:44 -040042 case kARGB_4444_SkColorType:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043 *c = SkUnPreMultiply::PMColorToColor(
44 SkPixel4444ToPixel32(*bitmap.getAddr16(x, y)));
45 break;
Mike Reed1103b322014-07-08 12:36:44 -040046 case kIndex_8_SkColorType: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047 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
58static 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
Tom Hudsond8f904f2015-10-28 20:35:36 +000064static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 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);
Tom Hudsond8f904f2015-10-28 20:35:36 +000072 } else if (src.width() == 1 && src.height() == 1) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 SkColor c;
Tom Hudsond8f904f2015-10-28 20:35:36 +000074 if (!getColor(bitmap, src.fLeft, src.fTop, &c)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 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:
Leon Scroggins IIIf35b9892015-07-31 10:38:40 -040085 canvas->drawBitmapRect(bitmap, SkRect::Make(src), dst, &paint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 }
87}
88
89SkScalar 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);
Mike Reed47b34412015-05-13 12:16:57 -040095 return srcSpace * stretchySpaceRemaining / numStrechyPixelsRemaining;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096}
97
Derek Sollenberger4c5efe92015-07-10 13:56:39 -040098void NinePatch::Draw(SkCanvas* canvas, const SkRect& bounds,
99 const SkBitmap& bitmap, const Res_png_9patch& chunk,
100 const SkPaint* paint, SkRegion** outRegion) {
Derek Sollenbergerca79cf62012-08-14 16:44:52 -0400101 if (canvas && canvas->quickReject(bounds)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 return;
103 }
Mike Reed211db4a2009-09-11 09:36:35 -0400104
105 SkPaint defaultPaint;
106 if (NULL == paint) {
107 // matches default dither in NinePatchDrawable.java.
108 defaultPaint.setDither(true);
109 paint = &defaultPaint;
110 }
Narayan Kamath6381dd42014-03-03 17:12:03 +0000111
112 const int32_t* xDivs = chunk.getXDivs();
113 const int32_t* yDivs = chunk.getYDivs();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800115 if (kUseTrace) {
116 gTrace = true;
117 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118
119 SkASSERT(canvas || outRegion);
120
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800121 if (kUseTrace) {
122 if (canvas) {
123 const SkMatrix& m = canvas->getTotalMatrix();
124 ALOGV("ninepatch [%g %g %g] [%g %g %g]\n",
125 SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]),
126 SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5]));
127 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800129 ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()),
130 SkScalarToFloat(bounds.height()));
Steve Block71f2cf12011-10-20 11:56:00 +0100131 ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height());
Narayan Kamath6381dd42014-03-03 17:12:03 +0000132 ALOGV("======== ninepatch xDivs [%d,%d]\n", xDivs[0], xDivs[1]);
133 ALOGV("======== ninepatch yDivs [%d,%d]\n", yDivs[0], yDivs[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135
136 if (bounds.isEmpty() ||
137 bitmap.width() == 0 || bitmap.height() == 0 ||
138 (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
139 {
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800140 if (kUseTrace) {
141 ALOGV("======== abort ninepatch draw\n");
142 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 return;
144 }
145
146 // should try a quick-reject test before calling lockPixels
147
148 SkAutoLockPixels alp(bitmap);
149 // after the lock, it is valid to check getPixels()
150 if (bitmap.getPixels() == NULL)
151 return;
152
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 const bool hasXfer = paint->getXfermode() != NULL;
154 SkRect dst;
155 SkIRect src;
156
Narayan Kamath6381dd42014-03-03 17:12:03 +0000157 const int32_t x0 = xDivs[0];
158 const int32_t y0 = yDivs[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159 const SkColor initColor = ((SkPaint*)paint)->getColor();
160 const uint8_t numXDivs = chunk.numXDivs;
161 const uint8_t numYDivs = chunk.numYDivs;
162 int i;
163 int j;
164 int colorIndex = 0;
165 uint32_t color;
166 bool xIsStretchable;
167 const bool initialXIsStretchable = (x0 == 0);
168 bool yIsStretchable = (y0 == 0);
169 const int bitmapWidth = bitmap.width();
170 const int bitmapHeight = bitmap.height();
171
Leon Scroggins III462dd012015-05-21 09:48:15 -0400172 // Number of bytes needed for dstRights array.
173 // Need to cast numXDivs to a larger type to avoid overflow.
174 const size_t dstBytes = ((size_t) numXDivs + 1) * sizeof(SkScalar);
175 SkScalar* dstRights = (SkScalar*) alloca(dstBytes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800176 bool dstRightsHaveBeenCached = false;
177
178 int numStretchyXPixelsRemaining = 0;
179 for (i = 0; i < numXDivs; i += 2) {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000180 numStretchyXPixelsRemaining += xDivs[i + 1] - xDivs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
182 int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
183 int numStretchyYPixelsRemaining = 0;
184 for (i = 0; i < numYDivs; i += 2) {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000185 numStretchyYPixelsRemaining += yDivs[i + 1] - yDivs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 }
187 int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
188
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800189 if (kUseTrace) {
190 ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
191 bitmap.width(), bitmap.height(),
192 SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
193 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
194 numXDivs, numYDivs);
195 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196
197 src.fTop = 0;
198 dst.fTop = bounds.fTop;
199 // The first row always starts with the top being at y=0 and the bottom
Roger Hu8b5f80e2013-12-30 18:53:51 +0000200 // being either yDivs[1] (if yDivs[0]=0) or yDivs[0]. In the former case
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201 // the first row is stretchable along the Y axis, otherwise it is fixed.
202 // The last row always ends with the bottom being bitmap.height and the top
203 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
204 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
205 // the Y axis, otherwise it is fixed.
206 //
207 // The first and last columns are similarly treated with respect to the X
208 // axis.
209 //
210 // The above is to help explain some of the special casing that goes on the
211 // code below.
212
213 // The initial yDiv and whether the first row is considered stretchable or
214 // not depends on whether yDiv[0] was zero or not.
215 for (j = yIsStretchable ? 1 : 0;
216 j <= numYDivs && src.fTop < bitmapHeight;
217 j++, yIsStretchable = !yIsStretchable) {
218 src.fLeft = 0;
219 dst.fLeft = bounds.fLeft;
220 if (j == numYDivs) {
221 src.fBottom = bitmapHeight;
222 dst.fBottom = bounds.fBottom;
223 } else {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000224 src.fBottom = yDivs[j];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 const int srcYSize = src.fBottom - src.fTop;
226 if (yIsStretchable) {
227 dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop,
228 srcYSize,
229 numStretchyYPixelsRemaining,
230 numFixedYPixelsRemaining);
231 numStretchyYPixelsRemaining -= srcYSize;
232 } else {
233 dst.fBottom = dst.fTop + SkIntToScalar(srcYSize);
234 numFixedYPixelsRemaining -= srcYSize;
235 }
236 }
237
238 xIsStretchable = initialXIsStretchable;
239 // The initial xDiv and whether the first column is considered
240 // stretchable or not depends on whether xDiv[0] was zero or not.
Narayan Kamath6381dd42014-03-03 17:12:03 +0000241 const uint32_t* colors = chunk.getColors();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 for (i = xIsStretchable ? 1 : 0;
243 i <= numXDivs && src.fLeft < bitmapWidth;
244 i++, xIsStretchable = !xIsStretchable) {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000245 color = colors[colorIndex++];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 if (i == numXDivs) {
247 src.fRight = bitmapWidth;
248 dst.fRight = bounds.fRight;
249 } else {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000250 src.fRight = xDivs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 if (dstRightsHaveBeenCached) {
252 dst.fRight = dstRights[i];
253 } else {
254 const int srcXSize = src.fRight - src.fLeft;
255 if (xIsStretchable) {
256 dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft,
257 srcXSize,
258 numStretchyXPixelsRemaining,
259 numFixedXPixelsRemaining);
260 numStretchyXPixelsRemaining -= srcXSize;
261 } else {
262 dst.fRight = dst.fLeft + SkIntToScalar(srcXSize);
263 numFixedXPixelsRemaining -= srcXSize;
264 }
265 dstRights[i] = dst.fRight;
266 }
267 }
268 // If this horizontal patch is too small to be displayed, leave
269 // the destination left edge where it is and go on to the next patch
270 // in the source.
271 if (src.fLeft >= src.fRight) {
272 src.fLeft = src.fRight;
273 continue;
274 }
275 // Make sure that we actually have room to draw any bits
276 if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) {
277 goto nextDiv;
278 }
279 // If this patch is transparent, skip and don't draw.
280 if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) {
281 if (outRegion) {
282 if (*outRegion == NULL) {
283 *outRegion = new SkRegion();
284 }
285 SkIRect idst;
286 dst.round(&idst);
Steve Block6215d3f2012-01-04 20:05:49 +0000287 //ALOGI("Adding trans rect: (%d,%d)-(%d,%d)\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 // idst.fLeft, idst.fTop, idst.fRight, idst.fBottom);
289 (*outRegion)->op(idst, SkRegion::kUnion_Op);
290 }
291 goto nextDiv;
292 }
293 if (canvas) {
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800294 if (kUseTrace) {
295 ALOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
296 src.fLeft, src.fTop, src.width(), src.height(),
297 SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop),
298 SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height()));
299 if (2 == src.width() && SkIntToScalar(5) == dst.width()) {
300 ALOGV("--- skip patch\n");
301 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
304 color, hasXfer);
305 }
306
307nextDiv:
308 src.fLeft = src.fRight;
309 dst.fLeft = dst.fRight;
310 }
311 src.fTop = src.fBottom;
312 dst.fTop = dst.fBottom;
313 dstRightsHaveBeenCached = true;
314 }
315}
Derek Sollenberger4c5efe92015-07-10 13:56:39 -0400316
317} // namespace android