blob: 985f3fb668148de3c99bf1583d21ec032f49c2c8 [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();
Derek Sollenbergerd39d1af2011-05-16 13:09:42 -0400114 // 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.
Dan Albert46d84442014-11-18 16:07:51 -0800116 if ((false)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 SkNinePatch::DrawMesh(canvas, bounds, bitmap,
Narayan Kamath6381dd42014-03-03 17:12:03 +0000118 xDivs, chunk.numXDivs,
119 yDivs, chunk.numYDivs,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 paint);
121 return;
122 }
123
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800124 if (kUseTrace) {
125 gTrace = true;
126 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800127
128 SkASSERT(canvas || outRegion);
129
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800130 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800138 ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()),
139 SkScalarToFloat(bounds.height()));
Steve Block71f2cf12011-10-20 11:56:00 +0100140 ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height());
Narayan Kamath6381dd42014-03-03 17:12:03 +0000141 ALOGV("======== ninepatch xDivs [%d,%d]\n", xDivs[0], xDivs[1]);
142 ALOGV("======== ninepatch yDivs [%d,%d]\n", yDivs[0], yDivs[1]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144
145 if (bounds.isEmpty() ||
146 bitmap.width() == 0 || bitmap.height() == 0 ||
147 (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0))
148 {
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800149 if (kUseTrace) {
150 ALOGV("======== abort ninepatch draw\n");
151 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 const bool hasXfer = paint->getXfermode() != NULL;
163 SkRect dst;
164 SkIRect src;
165
Narayan Kamath6381dd42014-03-03 17:12:03 +0000166 const int32_t x0 = xDivs[0];
167 const int32_t y0 = yDivs[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 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
Leon Scroggins III462dd012015-05-21 09:48:15 -0400181 // 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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 bool dstRightsHaveBeenCached = false;
186
187 int numStretchyXPixelsRemaining = 0;
188 for (i = 0; i < numXDivs; i += 2) {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000189 numStretchyXPixelsRemaining += xDivs[i + 1] - xDivs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 }
191 int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining;
192 int numStretchyYPixelsRemaining = 0;
193 for (i = 0; i < numYDivs; i += 2) {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000194 numStretchyYPixelsRemaining += yDivs[i + 1] - yDivs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 }
196 int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining;
197
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800198 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205
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
Roger Hu8b5f80e2013-12-30 18:53:51 +0000209 // 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 -0800210 // 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 {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000233 src.fBottom = yDivs[j];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 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.
Narayan Kamath6381dd42014-03-03 17:12:03 +0000250 const uint32_t* colors = chunk.getColors();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 for (i = xIsStretchable ? 1 : 0;
252 i <= numXDivs && src.fLeft < bitmapWidth;
253 i++, xIsStretchable = !xIsStretchable) {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000254 color = colors[colorIndex++];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 if (i == numXDivs) {
256 src.fRight = bitmapWidth;
257 dst.fRight = bounds.fRight;
258 } else {
Narayan Kamath6381dd42014-03-03 17:12:03 +0000259 src.fRight = xDivs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 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);
Steve Block6215d3f2012-01-04 20:05:49 +0000296 //ALOGI("Adding trans rect: (%d,%d)-(%d,%d)\n",
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 // idst.fLeft, idst.fTop, idst.fRight, idst.fBottom);
298 (*outRegion)->op(idst, SkRegion::kUnion_Op);
299 }
300 goto nextDiv;
301 }
302 if (canvas) {
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800303 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 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}
Derek Sollenberger4c5efe92015-07-10 13:56:39 -0400325
326} // namespace android