blob: d37126c91e9a69bec78bfb9815fe2c0dfe43b61c [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 "SkPaint.h"
24#include "SkUnPreMultiply.h"
25
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026#include <utils/Log.h>
27
Derek Sollenberger4c5efe92015-07-10 13:56:39 -040028namespace android {
29
Andreas Gampeed6b9df2014-11-20 22:02:20 -080030static const bool kUseTrace = true;
31static bool gTrace = false;
32
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) {
Mike Reed1103b322014-07-08 12:36:44 -040034 switch (bitmap.colorType()) {
35 case kN32_SkColorType:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036 *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y));
37 break;
Mike Reed1103b322014-07-08 12:36:44 -040038 case kRGB_565_SkColorType:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039 *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y));
40 break;
Mike Reed1103b322014-07-08 12:36:44 -040041 case kARGB_4444_SkColorType:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042 *c = SkUnPreMultiply::PMColorToColor(
43 SkPixel4444ToPixel32(*bitmap.getAddr16(x, y)));
44 break;
Mike Reed1103b322014-07-08 12:36:44 -040045 case kIndex_8_SkColorType: {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 SkColorTable* ctable = bitmap.getColorTable();
47 *c = SkUnPreMultiply::PMColorToColor(
48 (*ctable)[*bitmap.getAddr8(x, y)]);
49 break;
50 }
51 default:
52 return false;
53 }
54 return true;
55}
56
57static SkColor modAlpha(SkColor c, int alpha) {
58 int scale = alpha + (alpha >> 7);
59 int a = SkColorGetA(c) * scale >> 8;
60 return SkColorSetA(c, a);
61}
62
Tom Hudsond8f904f2015-10-28 20:35:36 +000063static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 const SkBitmap& bitmap, const SkPaint& paint,
65 SkColor initColor, uint32_t colorHint,
66 bool hasXfer) {
67 if (colorHint != android::Res_png_9patch::NO_COLOR) {
68 ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha()));
69 canvas->drawRect(dst, paint);
70 ((SkPaint*)&paint)->setColor(initColor);
Tom Hudsond8f904f2015-10-28 20:35:36 +000071 } else if (src.width() == 1 && src.height() == 1) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 SkColor c;
Tom Hudsond8f904f2015-10-28 20:35:36 +000073 if (!getColor(bitmap, src.fLeft, src.fTop, &c)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 goto SLOW_CASE;
75 }
76 if (0 != c || hasXfer) {
77 SkColor prev = paint.getColor();
78 ((SkPaint*)&paint)->setColor(c);
79 canvas->drawRect(dst, paint);
80 ((SkPaint*)&paint)->setColor(prev);
81 }
82 } else {
83 SLOW_CASE:
Leon Scroggins IIIf35b9892015-07-31 10:38:40 -040084 canvas->drawBitmapRect(bitmap, SkRect::Make(src), dst, &paint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085 }
86}
87
88SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint,
89 int srcSpace, int numStrechyPixelsRemaining,
90 int numFixedPixelsRemaining) {
91 SkScalar spaceRemaining = boundsLimit - startingPoint;
92 SkScalar stretchySpaceRemaining =
93 spaceRemaining - SkIntToScalar(numFixedPixelsRemaining);
Mike Reed47b34412015-05-13 12:16:57 -040094 return srcSpace * stretchySpaceRemaining / numStrechyPixelsRemaining;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095}
96
Derek Sollenberger4c5efe92015-07-10 13:56:39 -040097void NinePatch::Draw(SkCanvas* canvas, const SkRect& bounds,
98 const SkBitmap& bitmap, const Res_png_9patch& chunk,
99 const SkPaint* paint, SkRegion** outRegion) {
Derek Sollenbergerca79cf62012-08-14 16:44:52 -0400100 if (canvas && canvas->quickReject(bounds)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 return;
102 }
Mike Reed211db4a2009-09-11 09:36:35 -0400103
104 SkPaint defaultPaint;
105 if (NULL == paint) {
106 // matches default dither in NinePatchDrawable.java.
107 defaultPaint.setDither(true);
108 paint = &defaultPaint;
109 }
Narayan Kamath6381dd42014-03-03 17:12:03 +0000110
111 const int32_t* xDivs = chunk.getXDivs();
112 const int32_t* yDivs = chunk.getYDivs();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800114 if (kUseTrace) {
115 gTrace = true;
116 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117
118 SkASSERT(canvas || outRegion);
119
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800120 if (kUseTrace) {
121 if (canvas) {
122 const SkMatrix& m = canvas->getTotalMatrix();
123 ALOGV("ninepatch [%g %g %g] [%g %g %g]\n",
124 SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]),
125 SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5]));
126 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800128 ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()),
129 SkScalarToFloat(bounds.height()));
Steve Block71f2cf12011-10-20 11:56:00 +0100130 ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height());
Narayan Kamath6381dd42014-03-03 17:12:03 +0000131 ALOGV("======== ninepatch xDivs [%d,%d]\n", xDivs[0], xDivs[1]);
132 ALOGV("======== ninepatch yDivs [%d,%d]\n", yDivs[0], yDivs[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134
135 if (bounds.isEmpty() ||
136 bitmap.width() == 0 || bitmap.height() == 0 ||
137 (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
138 {
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800139 if (kUseTrace) {
140 ALOGV("======== abort ninepatch draw\n");
141 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 return;
143 }
144
145 // should try a quick-reject test before calling lockPixels
146
147 SkAutoLockPixels alp(bitmap);
148 // after the lock, it is valid to check getPixels()
149 if (bitmap.getPixels() == NULL)
150 return;
151
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 const bool hasXfer = paint->getXfermode() != NULL;
153 SkRect dst;
154 SkIRect src;
155
Narayan Kamath6381dd42014-03-03 17:12:03 +0000156 const int32_t x0 = xDivs[0];
157 const int32_t y0 = yDivs[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 const SkColor initColor = ((SkPaint*)paint)->getColor();
159 const uint8_t numXDivs = chunk.numXDivs;
160 const uint8_t numYDivs = chunk.numYDivs;
161 int i;
162 int j;
163 int colorIndex = 0;
164 uint32_t color;
165 bool xIsStretchable;
166 const bool initialXIsStretchable = (x0 == 0);
167 bool yIsStretchable = (y0 == 0);
168 const int bitmapWidth = bitmap.width();
169 const int bitmapHeight = bitmap.height();
170
Leon Scroggins III462dd012015-05-21 09:48:15 -0400171 // Number of bytes needed for dstRights array.
172 // Need to cast numXDivs to a larger type to avoid overflow.
173 const size_t dstBytes = ((size_t) numXDivs + 1) * sizeof(SkScalar);
174 SkScalar* dstRights = (SkScalar*) alloca(dstBytes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 bool dstRightsHaveBeenCached = false;
176
177 int numStretchyXPixelsRemaining = 0;
178 for (i = 0; i < numXDivs; i += 2) {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000179 numStretchyXPixelsRemaining += xDivs[i + 1] - xDivs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
181 int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
182 int numStretchyYPixelsRemaining = 0;
183 for (i = 0; i < numYDivs; i += 2) {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000184 numStretchyYPixelsRemaining += yDivs[i + 1] - yDivs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 }
186 int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
187
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800188 if (kUseTrace) {
189 ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n",
190 bitmap.width(), bitmap.height(),
191 SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
192 SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()),
193 numXDivs, numYDivs);
194 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195
196 src.fTop = 0;
197 dst.fTop = bounds.fTop;
198 // The first row always starts with the top being at y=0 and the bottom
Roger Hu8b5f80e2013-12-30 18:53:51 +0000199 // 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 -0800200 // the first row is stretchable along the Y axis, otherwise it is fixed.
201 // The last row always ends with the bottom being bitmap.height and the top
202 // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
203 // yDivs[numYDivs-1]. In the former case the last row is stretchable along
204 // the Y axis, otherwise it is fixed.
205 //
206 // The first and last columns are similarly treated with respect to the X
207 // axis.
208 //
209 // The above is to help explain some of the special casing that goes on the
210 // code below.
211
212 // The initial yDiv and whether the first row is considered stretchable or
213 // not depends on whether yDiv[0] was zero or not.
214 for (j = yIsStretchable ? 1 : 0;
215 j <= numYDivs && src.fTop < bitmapHeight;
216 j++, yIsStretchable = !yIsStretchable) {
217 src.fLeft = 0;
218 dst.fLeft = bounds.fLeft;
219 if (j == numYDivs) {
220 src.fBottom = bitmapHeight;
221 dst.fBottom = bounds.fBottom;
222 } else {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000223 src.fBottom = yDivs[j];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 const int srcYSize = src.fBottom - src.fTop;
225 if (yIsStretchable) {
226 dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop,
227 srcYSize,
228 numStretchyYPixelsRemaining,
229 numFixedYPixelsRemaining);
230 numStretchyYPixelsRemaining -= srcYSize;
231 } else {
232 dst.fBottom = dst.fTop + SkIntToScalar(srcYSize);
233 numFixedYPixelsRemaining -= srcYSize;
234 }
235 }
236
237 xIsStretchable = initialXIsStretchable;
238 // The initial xDiv and whether the first column is considered
239 // stretchable or not depends on whether xDiv[0] was zero or not.
Narayan Kamath6381dd42014-03-03 17:12:03 +0000240 const uint32_t* colors = chunk.getColors();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 for (i = xIsStretchable ? 1 : 0;
242 i <= numXDivs && src.fLeft < bitmapWidth;
243 i++, xIsStretchable = !xIsStretchable) {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000244 color = colors[colorIndex++];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 if (i == numXDivs) {
246 src.fRight = bitmapWidth;
247 dst.fRight = bounds.fRight;
248 } else {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000249 src.fRight = xDivs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 if (dstRightsHaveBeenCached) {
251 dst.fRight = dstRights[i];
252 } else {
253 const int srcXSize = src.fRight - src.fLeft;
254 if (xIsStretchable) {
255 dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft,
256 srcXSize,
257 numStretchyXPixelsRemaining,
258 numFixedXPixelsRemaining);
259 numStretchyXPixelsRemaining -= srcXSize;
260 } else {
261 dst.fRight = dst.fLeft + SkIntToScalar(srcXSize);
262 numFixedXPixelsRemaining -= srcXSize;
263 }
264 dstRights[i] = dst.fRight;
265 }
266 }
267 // If this horizontal patch is too small to be displayed, leave
268 // the destination left edge where it is and go on to the next patch
269 // in the source.
270 if (src.fLeft >= src.fRight) {
271 src.fLeft = src.fRight;
272 continue;
273 }
274 // Make sure that we actually have room to draw any bits
275 if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) {
276 goto nextDiv;
277 }
278 // If this patch is transparent, skip and don't draw.
279 if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) {
280 if (outRegion) {
281 if (*outRegion == NULL) {
282 *outRegion = new SkRegion();
283 }
284 SkIRect idst;
285 dst.round(&idst);
Steve Block6215d3f2012-01-04 20:05:49 +0000286 //ALOGI("Adding trans rect: (%d,%d)-(%d,%d)\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287 // idst.fLeft, idst.fTop, idst.fRight, idst.fBottom);
288 (*outRegion)->op(idst, SkRegion::kUnion_Op);
289 }
290 goto nextDiv;
291 }
292 if (canvas) {
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800293 if (kUseTrace) {
294 ALOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n",
295 src.fLeft, src.fTop, src.width(), src.height(),
296 SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop),
297 SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height()));
298 if (2 == src.width() && SkIntToScalar(5) == dst.width()) {
299 ALOGV("--- skip patch\n");
300 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800301 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor,
303 color, hasXfer);
304 }
305
306nextDiv:
307 src.fLeft = src.fRight;
308 dst.fLeft = dst.fRight;
309 }
310 src.fTop = src.fBottom;
311 dst.fTop = dst.fBottom;
312 dstRightsHaveBeenCached = true;
313 }
314}
Derek Sollenberger4c5efe92015-07-10 13:56:39 -0400315
316} // namespace android